├── .editorconfig ├── .gitignore ├── .nvmrc ├── LICENSE.md ├── README.md ├── article-examples ├── configure-fastify-http-proxy.js └── configure-fastify-server.js ├── package-lock.json ├── package.json └── src └── server.js /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = tab 5 | indent_size = 4 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # Snowpack dependency directory (https://snowpack.dev/) 45 | web_modules/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | .parcel-cache 78 | 79 | # Next.js build output 80 | .next 81 | out 82 | 83 | # Nuxt.js build / generate output 84 | .nuxt 85 | dist 86 | 87 | # Gatsby files 88 | .cache/ 89 | # Comment in the public line in if your project uses Gatsby and not Next.js 90 | # https://nextjs.org/blog/next-9-1#public-directory-support 91 | # public 92 | 93 | # vuepress build output 94 | .vuepress/dist 95 | 96 | # Serverless directories 97 | .serverless/ 98 | 99 | # FuseBox cache 100 | .fusebox/ 101 | 102 | # DynamoDB Local files 103 | .dynamodb/ 104 | 105 | # TernJS port file 106 | .tern-port 107 | 108 | # Stores VSCode versions used for testing VSCode extensions 109 | .vscode-test 110 | 111 | # yarn v2 112 | .yarn/cache 113 | .yarn/unplugged 114 | .yarn/build-state.yml 115 | .yarn/install-state.gz 116 | .pnp.* 117 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 14.13.0 2 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Simon Plenderleith 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 | # Example Node.js API proxy server 2 | 3 | > This project contains an example proxy server which allows clients to make authenticated requests to an API. 4 | 5 | ## Article 6 | 7 | This project was created as a companion to an article I've written about 8 | creating a proxy server for API requests using Node.js: 9 | [How to securely call an authenticated API from your front end](https://simonplend.com/how-to-securely-call-an-authenticated-api-from-your-front-end/) 10 | 11 | ## Frameworks & Libraries used 12 | 13 | - [`fastify`](https://github.com/fastify/fastify) - "Fast and low overhead web framework, for Node.js" 14 | - [`fastify-http-proxy`](https://github.com/fastify/fastify-http-proxy) - "Proxy your http requests to another server, with hooks." 15 | - [`dotenv`](https://www.npmjs.com/package/dotenv) - "Dotenv is a zero-dependency module that loads environment variables from a .env file into `process.env`" 16 | 17 | ## Setup 18 | 19 | ```sh 20 | npm install 21 | ``` 22 | 23 | Create an `.env` file for use in development: 24 | 25 | ``` 26 | API_KEY=abc123 27 | ``` 28 | 29 | The `.env` file will be automatically loaded by `dotenv` when you run 30 | the example server (see below). 31 | 32 | ## Running the example server 33 | 34 | ```sh 35 | npm start 36 | ``` 37 | 38 | ## Development 39 | 40 | If you want to test out making changes to the example proxy server, 41 | I recommend installing [`nodemon`](https://nodemon.io/). It will watch for 42 | any file changes you make and take care of restarting the server for you. 43 | 44 | Once it is installed you can run it in your terminal: 45 | 46 | ```sh 47 | nodemon 48 | ``` 49 | -------------------------------------------------------------------------------- /article-examples/configure-fastify-http-proxy.js: -------------------------------------------------------------------------------- 1 | // src/server.js 2 | 3 | import fastifyHttpProxy from "fastify-http-proxy"; 4 | 5 | /** 6 | * Register and configure the `fastify-http-proxy` plugin. 7 | * 8 | * This plugin supports all the options of `fastify-reply-from`, 9 | * as well as a few additional options e.g. `upstream`. 10 | * 11 | * @see https://github.com/fastify/fastify-http-proxy#options 12 | * @see https://github.com/fastify/fastify-reply-from 13 | */ 14 | fastify.register(fastifyHttpProxy, { 15 | upstream: "https://some-api.com", 16 | undici: true, 17 | }); 18 | -------------------------------------------------------------------------------- /article-examples/configure-fastify-server.js: -------------------------------------------------------------------------------- 1 | // src/server.js 2 | 3 | import createFastifyServer from "fastify"; 4 | 5 | /** 6 | * Create a Fastify server instance with logging enabled. 7 | * Fastify uses the library `pino` for logging. 8 | * 9 | * @see https://www.fastify.io/docs/latest/Logging/ 10 | * @see https://github.com/pinojs/pino/ 11 | */ 12 | const fastify = createFastifyServer({ 13 | logger: true, 14 | }); 15 | 16 | try { 17 | /** 18 | * Make use of top-level `await` i.e. outside of an `async` function. 19 | * 20 | * @see https://nodejs.org/docs/latest-v14.x/api/esm.html#esm_top_level_await 21 | */ 22 | await fastify.listen(3000); 23 | } catch (error) { 24 | fastify.log.error(error); 25 | process.exit(1); 26 | } 27 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-nodejs-api-proxy-server", 3 | "requires": true, 4 | "lockfileVersion": 1, 5 | "dependencies": { 6 | "abstract-logging": { 7 | "version": "2.0.1", 8 | "resolved": "https://registry.npmjs.org/abstract-logging/-/abstract-logging-2.0.1.tgz", 9 | "integrity": "sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA==" 10 | }, 11 | "ajv": { 12 | "version": "6.12.6", 13 | "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", 14 | "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", 15 | "requires": { 16 | "fast-deep-equal": "^3.1.1", 17 | "fast-json-stable-stringify": "^2.0.0", 18 | "json-schema-traverse": "^0.4.1", 19 | "uri-js": "^4.2.2" 20 | } 21 | }, 22 | "archy": { 23 | "version": "1.0.0", 24 | "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", 25 | "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=" 26 | }, 27 | "atomic-sleep": { 28 | "version": "1.0.0", 29 | "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", 30 | "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==" 31 | }, 32 | "avvio": { 33 | "version": "7.2.1", 34 | "resolved": "https://registry.npmjs.org/avvio/-/avvio-7.2.1.tgz", 35 | "integrity": "sha512-b+gox68dqD6c3S3t+bZBKN6rYbVWdwpN12sHQLFTiacDT2rcq7fm07Ww+IKt/AvAkyCIe1f5ArP1bC/vAlx97A==", 36 | "requires": { 37 | "archy": "^1.0.0", 38 | "debug": "^4.0.0", 39 | "fastq": "^1.6.1", 40 | "queue-microtask": "^1.1.2" 41 | } 42 | }, 43 | "cookie": { 44 | "version": "0.4.1", 45 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", 46 | "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==" 47 | }, 48 | "debug": { 49 | "version": "4.3.1", 50 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", 51 | "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", 52 | "requires": { 53 | "ms": "2.1.2" 54 | } 55 | }, 56 | "deepmerge": { 57 | "version": "4.2.2", 58 | "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", 59 | "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==" 60 | }, 61 | "depd": { 62 | "version": "1.1.2", 63 | "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", 64 | "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" 65 | }, 66 | "dotenv": { 67 | "version": "8.2.0", 68 | "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.2.0.tgz", 69 | "integrity": "sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw==", 70 | "dev": true 71 | }, 72 | "end-of-stream": { 73 | "version": "1.4.4", 74 | "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", 75 | "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", 76 | "requires": { 77 | "once": "^1.4.0" 78 | } 79 | }, 80 | "fast-decode-uri-component": { 81 | "version": "1.0.1", 82 | "resolved": "https://registry.npmjs.org/fast-decode-uri-component/-/fast-decode-uri-component-1.0.1.tgz", 83 | "integrity": "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==" 84 | }, 85 | "fast-deep-equal": { 86 | "version": "3.1.3", 87 | "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", 88 | "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" 89 | }, 90 | "fast-json-stable-stringify": { 91 | "version": "2.1.0", 92 | "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", 93 | "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" 94 | }, 95 | "fast-json-stringify": { 96 | "version": "2.4.1", 97 | "resolved": "https://registry.npmjs.org/fast-json-stringify/-/fast-json-stringify-2.4.1.tgz", 98 | "integrity": "sha512-Nzqv9yf0JDIhWwA6GcSPKMPhHJYIexOLEw7psxZhPjnYlLuJ/SGEAihVC1whQu5bKJWE2VMmoGTcspv/6AGLVQ==", 99 | "requires": { 100 | "ajv": "^6.11.0", 101 | "deepmerge": "^4.2.2", 102 | "string-similarity": "^4.0.1" 103 | } 104 | }, 105 | "fast-redact": { 106 | "version": "3.0.0", 107 | "resolved": "https://registry.npmjs.org/fast-redact/-/fast-redact-3.0.0.tgz", 108 | "integrity": "sha512-a/S/Hp6aoIjx7EmugtzLqXmcNsyFszqbt6qQ99BdG61QjBZF6shNis0BYR6TsZOQ1twYc0FN2Xdhwwbv6+KD0w==" 109 | }, 110 | "fast-safe-stringify": { 111 | "version": "2.0.7", 112 | "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.0.7.tgz", 113 | "integrity": "sha512-Utm6CdzT+6xsDk2m8S6uL8VHxNwI6Jub+e9NYTcAms28T84pTa25GJQV9j0CY0N1rM8hK4x6grpF2BQf+2qwVA==" 114 | }, 115 | "fastify": { 116 | "version": "3.11.0", 117 | "resolved": "https://registry.npmjs.org/fastify/-/fastify-3.11.0.tgz", 118 | "integrity": "sha512-xc0mTG3cZhBrpsHh5M+xujA/xAIfBCn6DLOWPqCbIBkt0TtfeDaXpBEtlejWp13eM8SZbyXnHcR6PJaOajbY/A==", 119 | "requires": { 120 | "abstract-logging": "^2.0.0", 121 | "ajv": "^6.12.2", 122 | "avvio": "^7.1.2", 123 | "fast-json-stringify": "^2.2.1", 124 | "fastify-error": "^0.3.0", 125 | "fastify-warning": "^0.2.0", 126 | "find-my-way": "^3.0.5", 127 | "flatstr": "^1.0.12", 128 | "light-my-request": "^4.2.0", 129 | "pino": "^6.2.1", 130 | "proxy-addr": "^2.0.5", 131 | "readable-stream": "^3.4.0", 132 | "rfdc": "^1.1.4", 133 | "secure-json-parse": "^2.0.0", 134 | "semver": "^7.3.2", 135 | "tiny-lru": "^7.0.0" 136 | } 137 | }, 138 | "fastify-error": { 139 | "version": "0.3.0", 140 | "resolved": "https://registry.npmjs.org/fastify-error/-/fastify-error-0.3.0.tgz", 141 | "integrity": "sha512-Jm2LMTB5rsJqlS1+cmgqqM9tTs0UrlgYR7TvDT3ZgXsUI5ib1NjQlqZHf+tDK5tVPdFGwyq02wAoJtyYIRSiFA==" 142 | }, 143 | "fastify-http-proxy": { 144 | "version": "4.2.0", 145 | "resolved": "https://registry.npmjs.org/fastify-http-proxy/-/fastify-http-proxy-4.2.0.tgz", 146 | "integrity": "sha512-qxIj5AHrt4sgTVggv1km+dr1105gzHRk39/rhzuNIUy747m2WswsLM+ZeedulK/EGYSTNluKwNhbqwPSZ4JvnA==", 147 | "requires": { 148 | "fastify-reply-from": "^3.1.3", 149 | "ws": "^7.4.1" 150 | } 151 | }, 152 | "fastify-plugin": { 153 | "version": "3.0.0", 154 | "resolved": "https://registry.npmjs.org/fastify-plugin/-/fastify-plugin-3.0.0.tgz", 155 | "integrity": "sha512-ZdCvKEEd92DNLps5n0v231Bha8bkz1DjnPP/aEz37rz/q42Z5JVLmgnqR4DYuNn3NXAO3IDCPyRvgvxtJ4Ym4w==" 156 | }, 157 | "fastify-reply-from": { 158 | "version": "3.5.0", 159 | "resolved": "https://registry.npmjs.org/fastify-reply-from/-/fastify-reply-from-3.5.0.tgz", 160 | "integrity": "sha512-kNc0taosEyZz1DZBo/Dt4ihxF210gqalhRAiKFKT0VvGCL/+3A1JunlY/ExeoJUyfiCUWpRJ87pj4FCdrhbZLA==", 161 | "requires": { 162 | "end-of-stream": "^1.4.1", 163 | "fastify-plugin": "^3.0.0", 164 | "http-errors": "^1.8.0", 165 | "pump": "^3.0.0", 166 | "semver": "^7.2.1", 167 | "tiny-lru": "^7.0.0", 168 | "undici": "^2.1.0" 169 | } 170 | }, 171 | "fastify-warning": { 172 | "version": "0.2.0", 173 | "resolved": "https://registry.npmjs.org/fastify-warning/-/fastify-warning-0.2.0.tgz", 174 | "integrity": "sha512-s1EQguBw/9qtc1p/WTY4eq9WMRIACkj+HTcOIK1in4MV5aFaQC9ZCIt0dJ7pr5bIf4lPpHvAtP2ywpTNgs7hqw==" 175 | }, 176 | "fastq": { 177 | "version": "1.10.0", 178 | "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.10.0.tgz", 179 | "integrity": "sha512-NL2Qc5L3iQEsyYzweq7qfgy5OtXCmGzGvhElGEd/SoFWEMOEczNh5s5ocaF01HDetxz+p8ecjNPA6cZxxIHmzA==", 180 | "requires": { 181 | "reusify": "^1.0.4" 182 | } 183 | }, 184 | "find-my-way": { 185 | "version": "3.0.5", 186 | "resolved": "https://registry.npmjs.org/find-my-way/-/find-my-way-3.0.5.tgz", 187 | "integrity": "sha512-FweGg0cv1sBX8z7WhvBX5B5AECW4Zdh/NiB38Oa0qwSNIyPgRBCl/YjxuZn/rz38E/MMBHeVKJ22i7W3c626Gg==", 188 | "requires": { 189 | "fast-decode-uri-component": "^1.0.1", 190 | "safe-regex2": "^2.0.0", 191 | "semver-store": "^0.3.0" 192 | } 193 | }, 194 | "flatstr": { 195 | "version": "1.0.12", 196 | "resolved": "https://registry.npmjs.org/flatstr/-/flatstr-1.0.12.tgz", 197 | "integrity": "sha512-4zPxDyhCyiN2wIAtSLI6gc82/EjqZc1onI4Mz/l0pWrAlsSfYH/2ZIcU+e3oA2wDwbzIWNKwa23F8rh6+DRWkw==" 198 | }, 199 | "forwarded": { 200 | "version": "0.1.2", 201 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", 202 | "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" 203 | }, 204 | "http-errors": { 205 | "version": "1.8.0", 206 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.0.tgz", 207 | "integrity": "sha512-4I8r0C5JDhT5VkvI47QktDW75rNlGVsUf/8hzjCC/wkWI/jdTRmBb9aI7erSG82r1bjKY3F6k28WnsVxB1C73A==", 208 | "requires": { 209 | "depd": "~1.1.2", 210 | "inherits": "2.0.4", 211 | "setprototypeof": "1.2.0", 212 | "statuses": ">= 1.5.0 < 2", 213 | "toidentifier": "1.0.0" 214 | } 215 | }, 216 | "inherits": { 217 | "version": "2.0.4", 218 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 219 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" 220 | }, 221 | "ipaddr.js": { 222 | "version": "1.9.1", 223 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", 224 | "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" 225 | }, 226 | "json-schema-traverse": { 227 | "version": "0.4.1", 228 | "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", 229 | "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" 230 | }, 231 | "light-my-request": { 232 | "version": "4.4.1", 233 | "resolved": "https://registry.npmjs.org/light-my-request/-/light-my-request-4.4.1.tgz", 234 | "integrity": "sha512-FDNRF2mYjthIRWE7O8d/X7AzDx4otQHl4/QXbu3Q/FRwBFcgb+ZoDaUd5HwN53uQXLAiw76osN+Va0NEaOW6rQ==", 235 | "requires": { 236 | "ajv": "^6.12.2", 237 | "cookie": "^0.4.0", 238 | "fastify-warning": "^0.2.0", 239 | "readable-stream": "^3.6.0", 240 | "set-cookie-parser": "^2.4.1" 241 | } 242 | }, 243 | "lru-cache": { 244 | "version": "6.0.0", 245 | "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", 246 | "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", 247 | "requires": { 248 | "yallist": "^4.0.0" 249 | } 250 | }, 251 | "ms": { 252 | "version": "2.1.2", 253 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 254 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" 255 | }, 256 | "once": { 257 | "version": "1.4.0", 258 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 259 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 260 | "requires": { 261 | "wrappy": "1" 262 | } 263 | }, 264 | "pino": { 265 | "version": "6.11.0", 266 | "resolved": "https://registry.npmjs.org/pino/-/pino-6.11.0.tgz", 267 | "integrity": "sha512-VPqEE2sU1z6wqkTtr7DdTktayTNE/JgeuWjfXh9g/TI6X7venzv4gaoU24/jSywf6bBeDfZRHWEeO/6f8bNppA==", 268 | "requires": { 269 | "fast-redact": "^3.0.0", 270 | "fast-safe-stringify": "^2.0.7", 271 | "flatstr": "^1.0.12", 272 | "pino-std-serializers": "^3.1.0", 273 | "quick-format-unescaped": "^4.0.1", 274 | "sonic-boom": "^1.0.2" 275 | } 276 | }, 277 | "pino-std-serializers": { 278 | "version": "3.2.0", 279 | "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-3.2.0.tgz", 280 | "integrity": "sha512-EqX4pwDPrt3MuOAAUBMU0Tk5kR/YcCM5fNPEzgCO2zJ5HfX0vbiH9HbJglnyeQsN96Kznae6MWD47pZB5avTrg==" 281 | }, 282 | "proxy-addr": { 283 | "version": "2.0.6", 284 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz", 285 | "integrity": "sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw==", 286 | "requires": { 287 | "forwarded": "~0.1.2", 288 | "ipaddr.js": "1.9.1" 289 | } 290 | }, 291 | "pump": { 292 | "version": "3.0.0", 293 | "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", 294 | "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", 295 | "requires": { 296 | "end-of-stream": "^1.1.0", 297 | "once": "^1.3.1" 298 | } 299 | }, 300 | "punycode": { 301 | "version": "2.1.1", 302 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", 303 | "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" 304 | }, 305 | "queue-microtask": { 306 | "version": "1.2.2", 307 | "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.2.tgz", 308 | "integrity": "sha512-dB15eXv3p2jDlbOiNLyMabYg1/sXvppd8DP2J3EOCQ0AkuSXCW2tP7mnVouVLJKgUMY6yP0kcQDVpLCN13h4Xg==" 309 | }, 310 | "quick-format-unescaped": { 311 | "version": "4.0.1", 312 | "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.1.tgz", 313 | "integrity": "sha512-RyYpQ6Q5/drsJyOhrWHYMWTedvjTIat+FTwv0K4yoUxzvekw2aRHMQJLlnvt8UantkZg2++bEzD9EdxXqkWf4A==" 314 | }, 315 | "readable-stream": { 316 | "version": "3.6.0", 317 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", 318 | "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", 319 | "requires": { 320 | "inherits": "^2.0.3", 321 | "string_decoder": "^1.1.1", 322 | "util-deprecate": "^1.0.1" 323 | } 324 | }, 325 | "ret": { 326 | "version": "0.2.2", 327 | "resolved": "https://registry.npmjs.org/ret/-/ret-0.2.2.tgz", 328 | "integrity": "sha512-M0b3YWQs7R3Z917WRQy1HHA7Ba7D8hvZg6UE5mLykJxQVE2ju0IXbGlaHPPlkY+WN7wFP+wUMXmBFA0aV6vYGQ==" 329 | }, 330 | "reusify": { 331 | "version": "1.0.4", 332 | "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", 333 | "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==" 334 | }, 335 | "rfdc": { 336 | "version": "1.2.0", 337 | "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.2.0.tgz", 338 | "integrity": "sha512-ijLyszTMmUrXvjSooucVQwimGUk84eRcmCuLV8Xghe3UO85mjUtRAHRyoMM6XtyqbECaXuBWx18La3523sXINA==" 339 | }, 340 | "safe-buffer": { 341 | "version": "5.2.1", 342 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", 343 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" 344 | }, 345 | "safe-regex2": { 346 | "version": "2.0.0", 347 | "resolved": "https://registry.npmjs.org/safe-regex2/-/safe-regex2-2.0.0.tgz", 348 | "integrity": "sha512-PaUSFsUaNNuKwkBijoAPHAK6/eM6VirvyPWlZ7BAQy4D+hCvh4B6lIG+nPdhbFfIbP+gTGBcrdsOaUs0F+ZBOQ==", 349 | "requires": { 350 | "ret": "~0.2.0" 351 | } 352 | }, 353 | "secure-json-parse": { 354 | "version": "2.2.0", 355 | "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-2.2.0.tgz", 356 | "integrity": "sha512-OYpk8nU1g9+5u6HZ+OOMZVpD037Jo5E9QzdDdQRe6b9ZPWOoB85AenHz5Rd90UwG8zdef69dO0axSosdBsDK9Q==" 357 | }, 358 | "semver": { 359 | "version": "7.3.4", 360 | "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz", 361 | "integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==", 362 | "requires": { 363 | "lru-cache": "^6.0.0" 364 | } 365 | }, 366 | "semver-store": { 367 | "version": "0.3.0", 368 | "resolved": "https://registry.npmjs.org/semver-store/-/semver-store-0.3.0.tgz", 369 | "integrity": "sha512-TcZvGMMy9vodEFSse30lWinkj+JgOBvPn8wRItpQRSayhc+4ssDs335uklkfvQQJgL/WvmHLVj4Ycv2s7QCQMg==" 370 | }, 371 | "set-cookie-parser": { 372 | "version": "2.4.7", 373 | "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.4.7.tgz", 374 | "integrity": "sha512-VaSdYN1DlYuKOzBKqhYJnwaPeirZdNNUNmYdnp9/6Umr9s8amidctYitrX2Gk8wCqiBuiG5mpOYCiVhG5o4iMQ==" 375 | }, 376 | "setprototypeof": { 377 | "version": "1.2.0", 378 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", 379 | "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" 380 | }, 381 | "sonic-boom": { 382 | "version": "1.3.0", 383 | "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-1.3.0.tgz", 384 | "integrity": "sha512-4nX6OYvOYr6R76xfQKi6cZpTO3YSWe/vd+QdIfoH0lBy0MnPkeAbb2rRWgmgADkXUeCKPwO1FZAKlAVWAadELw==", 385 | "requires": { 386 | "atomic-sleep": "^1.0.0", 387 | "flatstr": "^1.0.12" 388 | } 389 | }, 390 | "statuses": { 391 | "version": "1.5.0", 392 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", 393 | "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" 394 | }, 395 | "string-similarity": { 396 | "version": "4.0.4", 397 | "resolved": "https://registry.npmjs.org/string-similarity/-/string-similarity-4.0.4.tgz", 398 | "integrity": "sha512-/q/8Q4Bl4ZKAPjj8WerIBJWALKkaPRfrvhfF8k/B23i4nzrlRj2/go1m90In7nG/3XDSbOo0+pu6RvCTM9RGMQ==" 399 | }, 400 | "string_decoder": { 401 | "version": "1.3.0", 402 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", 403 | "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", 404 | "requires": { 405 | "safe-buffer": "~5.2.0" 406 | } 407 | }, 408 | "tiny-lru": { 409 | "version": "7.0.6", 410 | "resolved": "https://registry.npmjs.org/tiny-lru/-/tiny-lru-7.0.6.tgz", 411 | "integrity": "sha512-zNYO0Kvgn5rXzWpL0y3RS09sMK67eGaQj9805jlK9G6pSadfriTczzLHFXa/xcW4mIRfmlB9HyQ/+SgL0V1uow==" 412 | }, 413 | "toidentifier": { 414 | "version": "1.0.0", 415 | "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", 416 | "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" 417 | }, 418 | "undici": { 419 | "version": "2.2.1", 420 | "resolved": "https://registry.npmjs.org/undici/-/undici-2.2.1.tgz", 421 | "integrity": "sha512-21sJmMvJOMsyt/2pQPgB5Ruvm2ADTTm34NHRy4kzfeW9uMO7gK2oN0f+5KaJCmoKGJb8KxdU6yWpW0SphFHadw==" 422 | }, 423 | "uri-js": { 424 | "version": "4.4.1", 425 | "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", 426 | "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", 427 | "requires": { 428 | "punycode": "^2.1.0" 429 | } 430 | }, 431 | "util-deprecate": { 432 | "version": "1.0.2", 433 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", 434 | "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" 435 | }, 436 | "wrappy": { 437 | "version": "1.0.2", 438 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 439 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" 440 | }, 441 | "ws": { 442 | "version": "7.4.2", 443 | "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.2.tgz", 444 | "integrity": "sha512-T4tewALS3+qsrpGI/8dqNMLIVdq/g/85U98HPMa6F0m6xTbvhXU6RCQLqPH3+SlomNV/LdY6RXEbBpMH6EOJnA==" 445 | }, 446 | "yallist": { 447 | "version": "4.0.0", 448 | "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", 449 | "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" 450 | } 451 | } 452 | } 453 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-nodejs-api-proxy-server", 3 | "type": "module", 4 | "author": "Simon Plenderleith", 5 | "license": "MIT", 6 | "engines": { 7 | "node": ">=14.13.0" 8 | }, 9 | "scripts": { 10 | "start": "node -r dotenv/config src/server.js" 11 | }, 12 | "dependencies": { 13 | "fastify": "^3.7.0", 14 | "fastify-http-proxy": "^4.0.4" 15 | }, 16 | "devDependencies": { 17 | "dotenv": "^8.2.0" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/server.js: -------------------------------------------------------------------------------- 1 | // src/server.js 2 | 3 | import createFastifyServer from "fastify"; 4 | import fastifyHttpProxy from "fastify-http-proxy"; 5 | 6 | /** 7 | * Create a Fastify server instance with logging enabled. 8 | * Fastify uses the library `pino` for logging. 9 | * 10 | * @see https://www.fastify.io/docs/latest/Logging/ 11 | * @see https://github.com/pinojs/pino/ 12 | */ 13 | const fastify = createFastifyServer({ 14 | logger: true, 15 | }); 16 | 17 | const CONFIG = { 18 | apiKey: process.env.API_KEY, 19 | }; 20 | 21 | /** 22 | * Register and configure the `fastify-http-proxy` plugin. 23 | * 24 | * This plugin supports all the options of `fastify-reply-from`, 25 | * as well as a few additional options e.g. `upstream`. 26 | * 27 | * @see https://github.com/fastify/fastify-http-proxy#options 28 | * @see https://github.com/fastify/fastify-reply-from 29 | */ 30 | fastify.register(fastifyHttpProxy, { 31 | upstream: "http://localhost:8000", 32 | undici: true, 33 | /** 34 | * Reply options to be passed through to `fastify-reply-from`, 35 | * which `fastify-http-proxy` uses under the hood. 36 | * 37 | * @see https://github.com/fastify/fastify-http-proxy#replyoptions 38 | */ 39 | replyOptions: { 40 | /** 41 | * @see https://github.com/fastify/fastify-reply-from#rewriterequestheadersoriginalreq-headers 42 | */ 43 | rewriteRequestHeaders: (originalRequest, headers) => { 44 | return { 45 | /** 46 | * Preserve the existing request headers. 47 | */ 48 | ...headers, 49 | /** 50 | * Add the header which the API we're proxying requests 51 | * to requires to authenticate the request. 52 | */ 53 | 'X-Api-Key': CONFIG.apiKey, 54 | }; 55 | }, 56 | }, 57 | }); 58 | 59 | try { 60 | /** 61 | * Make use of top-level `await` i.e. outside of an `async` function. 62 | * 63 | * @see https://nodejs.org/docs/latest-v14.x/api/esm.html#esm_top_level_await 64 | */ 65 | await fastify.listen(3000); 66 | } catch (error) { 67 | fastify.log.error(error); 68 | process.exit(1); 69 | } 70 | --------------------------------------------------------------------------------