├── LICENSE ├── README.md ├── azure-pipelines.yml ├── graphiql ├── Readme.md └── index.html ├── graphql-api ├── .gitignore ├── .vscode │ ├── extensions.json │ ├── launch.json │ ├── settings.json │ └── tasks.json ├── Readme.md ├── api │ ├── function.json │ └── index.js ├── extensions.csproj ├── host.json ├── package-lock.json ├── package.json └── proxies.json └── services ├── .gitignore ├── .vscode ├── extensions.json ├── launch.json ├── settings.json └── tasks.json ├── GetScore ├── function.json └── index.js ├── IncrementPoints ├── function.json └── index.js ├── Readme.md ├── extensions.csproj ├── host.json ├── local.settings.example.json ├── package-lock.json ├── package.json ├── proxies.json └── shared ├── error.js └── utils.js /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Simona Cotin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # serverless-graphql-workshop 2 | 3 | In this workshop we'll build a Serverless GraphQL endpoint for an existing voting API. 4 | 5 | ## slides 6 | 7 | [Build Scalable APIs Using GraphQL and Serverless](https://www.slideshare.net/SimonaCotin/build-scalable-apis-using-graphql-and-serverless-172329195) 8 | 9 | ## Prerequisites 10 | 11 | 1. A recent version of [Node](https://nodejs.org/en/download) (8+) 12 | 13 | 1. [VS Code](https://code.visualstudio.com/download/?WT.mc_id=graphqlworkshop-github-sicotin) 14 | 15 | 1. [Azure Functions CLI](https://docs.microsoft.com/azure/azure-functions/functions-run-local?WT.mc_id=graphqlworkshop-github-sicotin) 16 | 17 | 1. [Azure Functions Extension for VS Code](https://marketplace.visualstudio.com/items/?WT.mc_id=graphqlworkshop-github-sicotin&itemName=ms-azuretools.vscode-azurefunctions) 18 | 19 | 1. [Free Azure Account](https://azure.microsoft.com/en-us/free/?wt.mc_id=graphqlworkshop-github-sicotin) 20 | 21 | ## Steps 22 | 23 | 1. Create your own services following steps on [services readme](https://github.com/simonaco/serverless-graphql-workshop/blob/master/services/Readme.md) 24 | 25 | 1. Create GraphQL endpoint following steps on [api readme](https://github.com/simonaco/serverless-graphql-workshop/blob/master/graphql-api/Readme.md) 26 | 27 | 1. Add GraphiQL UI using steps in [readme](https://github.com/simonaco/serverless-graphql-workshop/blob/master/graphiql/Readme.md) 28 | 29 | ## Demo app 30 | 31 | GraphQL endpoint: [https://graphqlplayground.azurewebsites.net/api/graphql](https://graphqlplayground.azurewebsites.net/api/graphql) 32 | 33 | GraphiQL endpoint: 34 | [https://graphqlplayground.azurewebsites.net/api/graphiql](https://graphqlplayground.azurewebsites.net/api/graphiql) 35 | 36 | Sample query: 37 | 38 | ``` 39 | query { 40 | teams { 41 | id 42 | name 43 | points 44 | } 45 | } 46 | ``` 47 | 48 | Sample mutation: 49 | 50 | ``` 51 | mutation { 52 | incrementPoints(id:2) { 53 | id 54 | name 55 | points 56 | } 57 | } 58 | ``` 59 | --- 60 | 61 | 🎉 Congrats!! You made it - built your very first GraphQL endpoint on Serverless! 🎉 62 | -------------------------------------------------------------------------------- /azure-pipelines.yml: -------------------------------------------------------------------------------- 1 | pool: 2 | vmImage: 'Ubuntu 16.04' 3 | steps: 4 | - script: | 5 | npm install 6 | displayName: 'npm install' 7 | workingDirectory: '$(System.DefaultWorkingDirectory)/services' 8 | - task: ArchiveFiles@2 9 | displayName: 'Archive files' 10 | inputs: 11 | rootFolderOrFile: '$(System.DefaultWorkingDirectory)/services' 12 | includeRootFolder: false 13 | archiveFile: '$(System.DefaultWorkingDirectory)/$(Build.BuildId).zip' 14 | - task: PublishBuildArtifacts@1 15 | displayName: 'Publish files' 16 | inputs: 17 | PathtoPublish: '$(System.DefaultWorkingDirectory)' 18 | name: 'drop' 19 | -------------------------------------------------------------------------------- /graphiql/Readme.md: -------------------------------------------------------------------------------- 1 | # GraphiQL is an in-browser IDE for exploring GraphQL 2 | 3 | Example GraphiQL endpoint: https://graphqlplayground.azurewebsites.net/api/graphiql 4 | 5 | ## Steps: 6 | 7 | 1. ⚠️ Go to *graphiql* and open *index.html* 8 | 1. Replace the URL in graphQLFetcher function with your own GraphQL endpoint URL [https:///api/graphql](https:///api/graphql) 9 | 10 | 1. In the [Azure Portal](https://aka.ms/portal-nceu18) navigate to functions, click on the graphql function. From there, click on the *Platform features* tab -> API -> CORS and add your url as allowed origin 11 | 12 | 1. Run locally - use a local http server (i.e. http-server). If you don't have one installed: 13 | 14 | ``` 15 | npm i -g http-server 16 | ``` 17 | 18 | ### Extra steps 19 | 20 | 1. Deploy this as a static website in the cloud. You can use Azure Blob Storage following [steps here](https://docs.microsoft.com/azure/storage/blobs/storage-blob-static-website?WT.mc_id=graphqlworkshop-github-sicotin) 21 | 22 | 1. In your function app configure proxies to redirect traffic to your GraphiQL app 23 | 24 | ```json 25 | { 26 | "$schema": "http://json.schemastore.org/proxies", 27 | "proxies": { 28 | "graphiql": { 29 | "matchCondition": { 30 | "methods": ["GET"], 31 | "route": "/api/graphiql" 32 | }, 33 | "backendUri": "your_url_here" 34 | } 35 | } 36 | } 37 | ``` 38 | 39 | 1. Deploy your GraphQL api function app with the new proxy configured 40 | 41 | 1. Test GraphiQL by navigating to your function [https:///api/graphiql](https:///api/graphiql) 42 | 43 | --- 44 | 45 | ⚡ Congrats!! You have now completed all steps!! Go ask your instructor for a badge! ⚡ 46 | 47 | ### More resouces 48 | 49 | 1. To learn more about GraphiQL go to [GraphiQL](https://github.com/graphql/graphiql) 50 | 51 | 1. To learn more about proxies go to [Azure Proxies](https://docs.microsoft.com/azure/azure-functions/functions-proxies?WT.mc_id=graphqlworkshop-github-sicotin) 52 | 53 | -------------------------------------------------------------------------------- /graphiql/index.html: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | 11 | 12 | 24 | 25 | 32 | 33 | 34 | 35 | 36 | 37 | 42 | 43 | 44 | 45 | 46 | 47 | 48 |
Loading...
49 | 150 | 151 | 152 | -------------------------------------------------------------------------------- /graphql-api/.gitignore: -------------------------------------------------------------------------------- 1 | bin 2 | obj 3 | csx 4 | .vs 5 | edge 6 | Publish 7 | 8 | *.user 9 | *.suo 10 | *.cscfg 11 | *.Cache 12 | project.lock.json 13 | 14 | /packages 15 | /TestResults 16 | 17 | /tools/NuGet.exe 18 | /App_Data 19 | /secrets 20 | /data 21 | .secrets 22 | appsettings.json 23 | local.settings.json 24 | -------------------------------------------------------------------------------- /graphql-api/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "ms-azuretools.vscode-azurefunctions" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /graphql-api/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Attach to JavaScript Functions", 6 | "type": "node", 7 | "request": "attach", 8 | "port": 5858, 9 | "preLaunchTask": "runFunctionsHost" 10 | } 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /graphql-api/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "azureFunctions.projectRuntime": "~2", 3 | "azureFunctions.projectLanguage": "JavaScript", 4 | "azureFunctions.templateFilter": "Verified", 5 | "azureFunctions.deploySubpath": ".", 6 | "azureFunctions.preDeployTask": "installExtensions", 7 | "files.exclude": { 8 | "obj": true, 9 | "bin": true 10 | }, 11 | "debug.internalConsoleOptions": "neverOpen" 12 | } 13 | -------------------------------------------------------------------------------- /graphql-api/.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "runFunctionsHost", 6 | "type": "shell", 7 | "command": "func host start", 8 | "isBackground": true, 9 | "presentation": { 10 | "reveal": "always" 11 | }, 12 | "problemMatcher": "$func-watch", 13 | "options": { 14 | "env": { 15 | "languageWorkers__node__arguments": "--inspect=5858" 16 | } 17 | }, 18 | "dependsOn": "installExtensions" 19 | }, 20 | { 21 | "label": "installExtensions", 22 | "command": "func extensions install", 23 | "type": "shell", 24 | "presentation": { 25 | "reveal": "always" 26 | } 27 | } 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /graphql-api/Readme.md: -------------------------------------------------------------------------------- 1 | # GraphQL API 2 | 3 | This is a GraphQL endpoint calling two existing REST api endpoints 4 | 5 | You can explore the endpoint we'll build at [https://graphqlplayground.azurewebsites.net/api/graphiql](https://graphqlplayground.azurewebsites.net/api/graphiql) 6 | 7 | ## Steps 8 | 9 | If you haven't already, make sure to clone this repository. 10 | 11 | 1. ⚠️ Navigate to the *graphql-api* folder and open it in a *new instance* of VS Code 12 | 13 | ### Install dependencies 14 | 15 | ``` 16 | npm install 17 | ``` 18 | 19 | ### Run project 20 | There are two ways to run your project 21 | - Debug, we can through the Debug panel choose to run our project and that will enable us to set breakpoints for example 22 | - `npm start` this will behind the scenes run `fun host start`, which means we will start our backend as an Azure functions project, which this is is 23 | 24 | #### Run 25 | 26 | To just run the project without debugging type: 27 | 28 | ``` 29 | npm start 30 | ``` 31 | 32 | This will start our Azure functions server in the terminal and it will tell us at the end of that run what URL our service is found at. 33 | 34 | #### Debug 35 | 36 | 1. Go to *Debug* panel in VS Code and click run 37 | 38 | #### Query 39 | 40 | Normally your port `7071` should be free, that's the port Azure Functions normally start at, if it isn't, for some reason, then please go into `package.json` and change the `npm start` value from: 41 | 42 | ``` 43 | func host start 44 | ``` 45 | TO 46 | ``` 47 | func host start --port 7072 48 | ``` 49 | 50 | 1. Using [Postman](https://www.getpostman.com/) (or any Rest client) make a POST request to your GraphQL endpoint running in your local [http://localhost:7071/api/graphql](http://localhost:7071/api/graphql) with this body of type *application/json*: 51 | 52 | ```json 53 | { "query": "{teams{id name points}}"} 54 | ``` 55 | 56 | You should get an output looking like this: 57 | 58 | ``` 59 | { 60 | "data": { 61 | "teams": [ 62 | { 63 | "id": "1", 64 | "name": "red", 65 | "points": 19293 66 | }, 67 | { 68 | "id": "2", 69 | "name": "green", 70 | "points": 10516 71 | } 72 | ] 73 | } 74 | } 75 | ``` 76 | 77 | 1. If you have deployed your previous services to Azure, replace the URLs in index.js with your own 78 | 79 | 1. Run the function again and make sure you get the correct data back (coming from your own DB instance) 80 | 81 | 1. To deploy your function using VS Code go to the Azure Functions extension. Click the blue arrow button and follow prompt instructions to either create a new function or deploy to an existing one -------------------------------------------------------------------------------- /graphql-api/api/function.json: -------------------------------------------------------------------------------- 1 | { 2 | "disabled": false, 3 | "bindings": [ 4 | { 5 | "authLevel": "anonymous", 6 | "type": "httpTrigger", 7 | "direction": "in", 8 | "name": "req", 9 | "methods": ["get", "post"], 10 | "route": "graphql" 11 | }, 12 | { 13 | "type": "http", 14 | "direction": "out", 15 | "name": "res" 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /graphql-api/api/index.js: -------------------------------------------------------------------------------- 1 | const { graphql, buildSchema } = require('graphql'); 2 | const axios = require('axios'); 3 | const typeDefs = buildSchema(` 4 | type Team { 5 | id: ID 6 | name: String 7 | points: Int 8 | } 9 | type Query { 10 | teams: [Team] 11 | } 12 | type Mutation { 13 | incrementPoints(id: ID!): Team 14 | } 15 | `); 16 | 17 | const root = { 18 | teams: () => { 19 | return axios 20 | .get('https://graphqlvoting.azurewebsites.net/api/score') 21 | .then(res => res.data); 22 | }, 23 | incrementPoints: obj => { 24 | return axios 25 | .get(`https://graphqlvoting.azurewebsites.net/api/score/${obj.id}`) 26 | .then(res => res.data); 27 | } 28 | }; 29 | 30 | module.exports = async function(context, req) { 31 | const body = req.body; 32 | context.log(`GraphQL request: ${body}`); 33 | 34 | const response = await graphql( 35 | typeDefs, 36 | body.query, 37 | root, 38 | null, 39 | body.variables, 40 | body.operationName 41 | ); 42 | context.res = { 43 | body: response 44 | }; 45 | }; 46 | -------------------------------------------------------------------------------- /graphql-api/extensions.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | netstandard2.0 4 | 5 | ** 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /graphql-api/host.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0" 3 | } 4 | -------------------------------------------------------------------------------- /graphql-api/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "graphql-api", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "axios": { 8 | "version": "0.18.1", 9 | "resolved": "http://registry.npmjs.org/axios/-/axios-0.18.1.tgz", 10 | "integrity": "sha512-0BfJq4NSfQXd+SkFdrvFbG7addhYSBA2mQwISr46pD6E5iqkWg02RAs8vyTT/j0RTnoYmeXauBuSv1qKwR179g==", 11 | "requires": { 12 | "follow-redirects": "1.5.10", 13 | "is-buffer": "^2.0.2" 14 | } 15 | }, 16 | "debug": { 17 | "version": "3.1.0", 18 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", 19 | "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", 20 | "requires": { 21 | "ms": "2.0.0" 22 | } 23 | }, 24 | "follow-redirects": { 25 | "version": "1.5.10", 26 | "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz", 27 | "integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==", 28 | "requires": { 29 | "debug": "=3.1.0" 30 | } 31 | }, 32 | "graphql": { 33 | "version": "14.0.2", 34 | "resolved": "https://registry.npmjs.org/graphql/-/graphql-14.0.2.tgz", 35 | "integrity": "sha512-gUC4YYsaiSJT1h40krG3J+USGlwhzNTXSb4IOZljn9ag5Tj+RkoXrWp+Kh7WyE3t1NCfab5kzCuxBIvOMERMXw==", 36 | "requires": { 37 | "iterall": "^1.2.2" 38 | } 39 | }, 40 | "is-buffer": { 41 | "version": "2.0.4", 42 | "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.4.tgz", 43 | "integrity": "sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A==" 44 | }, 45 | "iterall": { 46 | "version": "1.2.2", 47 | "resolved": "https://registry.npmjs.org/iterall/-/iterall-1.2.2.tgz", 48 | "integrity": "sha512-yynBb1g+RFUPY64fTrFv7nsjRrENBQJaX2UL+2Szc9REFrSNm1rpSXHGzhmAy7a9uv3vlvgBlXnf9RqmPH1/DA==" 49 | }, 50 | "ms": { 51 | "version": "2.0.0", 52 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 53 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /graphql-api/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "graphql-api", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "func host start", 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "ISC", 13 | "dependencies": { 14 | "axios": "^0.18.0", 15 | "graphql": "^14.0.2" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /graphql-api/proxies.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/proxies", 3 | "proxies": {} 4 | } 5 | -------------------------------------------------------------------------------- /services/.gitignore: -------------------------------------------------------------------------------- 1 | bin 2 | obj 3 | csx 4 | .vs 5 | edge 6 | Publish 7 | 8 | *.user 9 | *.suo 10 | *.cscfg 11 | *.Cache 12 | project.lock.json 13 | 14 | /packages 15 | /TestResults 16 | 17 | /tools/NuGet.exe 18 | /App_Data 19 | /secrets 20 | /data 21 | .secrets 22 | appsettings.json 23 | local.settings.json 24 | node_modules 25 | -------------------------------------------------------------------------------- /services/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "ms-azuretools.vscode-azurefunctions" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /services/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Attach to JavaScript Functions", 6 | "type": "node", 7 | "request": "attach", 8 | "port": 5858, 9 | "preLaunchTask": "runFunctionsHost" 10 | } 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /services/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "azureFunctions.projectRuntime": "~2", 3 | "azureFunctions.projectLanguage": "JavaScript", 4 | "azureFunctions.templateFilter": "Verified", 5 | "azureFunctions.deploySubpath": ".", 6 | "azureFunctions.preDeployTask": "installExtensions", 7 | "files.exclude": { 8 | "obj": true, 9 | "bin": true 10 | }, 11 | "debug.internalConsoleOptions": "neverOpen" 12 | } 13 | -------------------------------------------------------------------------------- /services/.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "runFunctionsHost", 6 | "type": "shell", 7 | "command": "func host start", 8 | "isBackground": true, 9 | "presentation": { 10 | "reveal": "always" 11 | }, 12 | "problemMatcher": "$func-watch", 13 | "options": { 14 | "env": { 15 | "languageWorkers__node__arguments": "--inspect=5858" 16 | } 17 | }, 18 | "dependsOn": "installExtensions" 19 | }, 20 | { 21 | "label": "installExtensions", 22 | "command": "func extensions install", 23 | "type": "shell", 24 | "presentation": { 25 | "reveal": "always" 26 | } 27 | } 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /services/GetScore/function.json: -------------------------------------------------------------------------------- 1 | { 2 | "disabled": false, 3 | "bindings": [ 4 | { 5 | "authLevel": "anonymous", 6 | "type": "httpTrigger", 7 | "direction": "in", 8 | "name": "req", 9 | "methods": ["get"], 10 | "route": "score" 11 | }, 12 | { 13 | "type": "http", 14 | "direction": "out", 15 | "name": "res" 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /services/GetScore/index.js: -------------------------------------------------------------------------------- 1 | const conn = require('./../shared/utils'); 2 | const handleError = require('./../shared/error'); 3 | module.exports = function(context) { 4 | conn 5 | .connect() 6 | .then(client => { 7 | query(client); 8 | }) 9 | .catch(err => handleError(500, err, context)); 10 | 11 | const query = client => { 12 | const db = client.db('admin'); 13 | 14 | db.collection('Team') 15 | .find() 16 | .toArray() 17 | .then(res => { 18 | context.log('This is a happy moment'); 19 | res.forEach(team => delete team._id); 20 | const teams = res; 21 | context.res = { 22 | //status: 200, 23 | body: teams 24 | }; 25 | context.done(); 26 | }) 27 | .catch(err => handleError(500, err.stack, context)); 28 | }; 29 | }; 30 | -------------------------------------------------------------------------------- /services/IncrementPoints/function.json: -------------------------------------------------------------------------------- 1 | { 2 | "disabled": false, 3 | "bindings": [ 4 | { 5 | "authLevel": "anonymous", 6 | "type": "httpTrigger", 7 | "direction": "in", 8 | "name": "req", 9 | "methods": ["get"], 10 | "route": "score/{id}" 11 | }, 12 | { 13 | "type": "http", 14 | "direction": "out", 15 | "name": "res" 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /services/IncrementPoints/index.js: -------------------------------------------------------------------------------- 1 | const conn = require('./../shared/utils'); 2 | const handleError = require('./../shared/error'); 3 | module.exports = function(context) { 4 | conn 5 | .connect() 6 | .then(client => { 7 | query(client); 8 | }) 9 | .catch(err => handleError(500, err, context)); 10 | const query = client => { 11 | const db = client.db('admin'); 12 | db.collection('Team') 13 | .findOneAndUpdate( 14 | { id: context.req.params.id }, 15 | { $inc: { points: 1 } }, 16 | { returnNewDocument: true } 17 | ) 18 | .then(team => { 19 | context.res = { 20 | body: team.value 21 | }; 22 | context.done(); 23 | }) 24 | .catch(err => handleError(500, err, context)); 25 | }; 26 | }; 27 | -------------------------------------------------------------------------------- /services/Readme.md: -------------------------------------------------------------------------------- 1 | # REST API 2 | 3 | This is a serverless API with two endpoints: 4 | 5 | 1. Endpoint that lists teams available: [https://graphqlvoting.azurewebsites.net/api/score](https://graphqlvoting.azurewebsites.net/api/score) 6 | 7 | 1. Endoint that allows increasing points:[https://graphqlvoting.azurewebsites.net/api/score/{id}](https://graphqlvoting.azurewebsites.net/api/score/2) 8 | 9 | ## Run locally 10 | 11 | 1. Fork and clone the repository in your local 12 | 13 | 1. ⚠️ Open the *services* folder in VS Code 14 | 15 | 1. In VS Code, go to the Azure icon on the left hand side panel and in the Functions extension click on *Create new project* icon to initialize a new function app. *Overwrite all existing settings* 16 | 17 | 1. Create a new [Azure Cosmos DB](https://docs.microsoft.com/azure/cosmos-db/?WT.mc_id=graphqlworkshop-github-sicotin) account using MongoDB API model. Create a database called *admin* and a collection called *Team* 18 | 19 | 1. Populate your collection with a couple of items. In *Data Explorer* -> *admin* -> *Team* -> *Documents* add new entries to retrieve later: 20 | 21 | ``` 22 | { 23 | "id" : "1", 24 | "name" : "red", 25 | "points" : 0 26 | } 27 | 28 | { 29 | "id" : "2", 30 | "name" : "blue", 31 | "points" : 11912 32 | } 33 | 34 | ``` 35 | 36 | 1. Replace the contents of your local.settings.json with the configs in local.settings.example.json. Next, add your CosmosDB settings. You can get the connection settings from the Azure portal - Settings -> Connection String 37 | 38 | ```json 39 | { 40 | "IsEncrypted": false, 41 | "Values": { 42 | "AzureWebJobsStorage": "", 43 | "FUNCTIONS_WORKER_RUNTIME": "node", 44 | "CosmosDBURL": 45 | "mongodb://:/?ssl=true", 46 | "CosmosDBUser": "", 47 | "CosmosDBPass": "" 48 | } 49 | } 50 | ``` 51 | 52 | 1. Install dependencies 53 | 54 | ``` 55 | npm install 56 | ``` 57 | 58 | 1. In VS Code go to the *Debugging* panel and click debug 59 | 60 | 1. Navigate to [http://localhost:7071/api/score](http://localhost:7071/api/score) and you should see a json similar to: 61 | 62 | ```json 63 | [ 64 | { 65 | "id": "1", 66 | "name": "red", 67 | "points": 0 68 | }, 69 | { 70 | "id": "2", 71 | "name": "blue", 72 | "points": 19113 73 | } 74 | ] 75 | ``` 76 | 77 | 1. Deploy your API to the clouds ☁️☁️! Using VS Code, click on the Azure icon on the left menu and in the Functions extension click the blue arrow button to deploy your function to the cloud. Follow steps displayed in the prompt to eitehr create a new function or deploy to an existing one. 78 | 79 | 1. Upload local settings to the cloud using the Functions extension. Navigate to the function you just created, click to expand, right click on *Application Settings* -> *Download Remote Settings* -> *Upload Local Settings* - replace all 80 | 81 | 1. Copy the url listed in the logs and open it in the browser to call your newly created function. i.e [https://.azurewebsites.net/api/score](https://.azurewebsites.net/api/score) 82 | 83 | *NOTE*: the link in the logs will give you a url that contains the Function name (i.e. GetScore); we configured our routes to custom ones in function.json (score & score/id) 84 | 85 | ### More resources 86 | 87 | 1. To learn more about Azure Functions checkout this awesome docs [page](https://docs.microsoft.com/azure/azure-functions/?WT.mc_id=graphqlworkshop-github-sicotin) 88 | 89 | 1. To learn more about Azure Cosmos DB checkout this awesome docs [page](https://docs.microsoft.com/azure/cosmos-db/?WT.mc_id=graphqlworkshop-github-sicotin) 90 | -------------------------------------------------------------------------------- /services/extensions.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | netstandard2.0 4 | 5 | ** 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /services/host.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0" 3 | } 4 | -------------------------------------------------------------------------------- /services/local.settings.example.json: -------------------------------------------------------------------------------- 1 | { 2 | "IsEncrypted": false, 3 | "Values": { 4 | "AzureWebJobsStorage": "", 5 | "FUNCTIONS_WORKER_RUNTIME": "node", 6 | "CosmosDBURL": 7 | "mongodb://:/?ssl=true", 8 | "CosmosDBUser": "", 9 | "CosmosDBPass": "" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /services/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "services", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "bson": { 8 | "version": "1.1.0", 9 | "resolved": "https://registry.npmjs.org/bson/-/bson-1.1.0.tgz", 10 | "integrity": "sha512-9Aeai9TacfNtWXOYarkFJRW2CWo+dRon+fuLZYJmvLV3+MiUp0bEI6IAZfXEIg7/Pl/7IWlLaDnhzTsD81etQA==" 11 | }, 12 | "memory-pager": { 13 | "version": "1.1.0", 14 | "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.1.0.tgz", 15 | "integrity": "sha512-Mf9OHV/Y7h6YWDxTzX/b4ZZ4oh9NSXblQL8dtPCOomOtZciEHxePR78+uHFLLlsk01A6jVHhHsQZZ/WcIPpnzg==", 16 | "optional": true 17 | }, 18 | "mongodb": { 19 | "version": "3.1.9", 20 | "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.1.9.tgz", 21 | "integrity": "sha512-f+Og32wK/ovzVlC1S6Ft7yjVTvNsAOs6pBpDrPd2/3wPO9ijNsQrTNntuECjOSxGZpPVl0aRqgHzF1e9e+KvnQ==", 22 | "requires": { 23 | "mongodb-core": "3.1.8", 24 | "safe-buffer": "^5.1.2" 25 | } 26 | }, 27 | "mongodb-core": { 28 | "version": "3.1.8", 29 | "resolved": "https://registry.npmjs.org/mongodb-core/-/mongodb-core-3.1.8.tgz", 30 | "integrity": "sha512-reWCqIRNehyuLaqaz5JMOmh3Xd8JIjNX34o8mnewXLK2Fyt/Ky6BZbU+X0OPzy8qbX+JZrOtnuay7ASCieTYZw==", 31 | "requires": { 32 | "bson": "^1.1.0", 33 | "require_optional": "^1.0.1", 34 | "safe-buffer": "^5.1.2", 35 | "saslprep": "^1.0.0" 36 | } 37 | }, 38 | "require_optional": { 39 | "version": "1.0.1", 40 | "resolved": "https://registry.npmjs.org/require_optional/-/require_optional-1.0.1.tgz", 41 | "integrity": "sha512-qhM/y57enGWHAe3v/NcwML6a3/vfESLe/sGM2dII+gEO0BpKRUkWZow/tyloNqJyN6kXSl3RyyM8Ll5D/sJP8g==", 42 | "requires": { 43 | "resolve-from": "^2.0.0", 44 | "semver": "^5.1.0" 45 | } 46 | }, 47 | "resolve-from": { 48 | "version": "2.0.0", 49 | "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-2.0.0.tgz", 50 | "integrity": "sha1-lICrIOlP+h2egKgEx+oUdhGWa1c=" 51 | }, 52 | "safe-buffer": { 53 | "version": "5.1.2", 54 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 55 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" 56 | }, 57 | "saslprep": { 58 | "version": "1.0.2", 59 | "resolved": "https://registry.npmjs.org/saslprep/-/saslprep-1.0.2.tgz", 60 | "integrity": "sha512-4cDsYuAjXssUSjxHKRe4DTZC0agDwsCqcMqtJAQPzC74nJ7LfAJflAtC1Zed5hMzEQKj82d3tuzqdGNRsLJ4Gw==", 61 | "optional": true, 62 | "requires": { 63 | "sparse-bitfield": "^3.0.3" 64 | } 65 | }, 66 | "semver": { 67 | "version": "5.6.0", 68 | "resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz", 69 | "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==" 70 | }, 71 | "sparse-bitfield": { 72 | "version": "3.0.3", 73 | "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", 74 | "integrity": "sha1-/0rm5oZWBWuks+eSqzM004JzyhE=", 75 | "optional": true, 76 | "requires": { 77 | "memory-pager": "^1.0.2" 78 | } 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /services/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "services", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "mongodb": "^3.1.9" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /services/proxies.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/proxies", 3 | "proxies": {} 4 | } 5 | -------------------------------------------------------------------------------- /services/shared/error.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | handleError: function(status, message, context) { 3 | context.res = { status: status, body: message }; 4 | context.done(); 5 | } 6 | }; 7 | -------------------------------------------------------------------------------- /services/shared/utils.js: -------------------------------------------------------------------------------- 1 | const MongoClient = require('mongodb').MongoClient; 2 | let client = null; 3 | module.exports = { 4 | connect: function() { 5 | const auth = { 6 | user: process.env.CosmosDBUser, 7 | password: process.env.CosmosDBPass 8 | }; 9 | return new Promise((resolve, reject) => { 10 | if (client == null) { 11 | MongoClient.connect( 12 | process.env.CosmosDBURL, 13 | { auth: auth } 14 | ) 15 | .then(_client => { 16 | client = _client; 17 | resolve(_client); 18 | }) 19 | .catch(err => { 20 | reject(err.stack); 21 | }); 22 | } else { 23 | resolve(client); 24 | } 25 | }); 26 | } 27 | }; 28 | --------------------------------------------------------------------------------