diff --git a/docs/02-express-middlewares-and-request-handlers.md b/docs/02-express-middlewares-and-request-handlers.md new file mode 100644 index 0000000..32bce85 --- /dev/null +++ b/docs/02-express-middlewares-and-request-handlers.md @@ -0,0 +1,6 @@ +## 2 – Express Middlewares and Request Handlers + +Now the server application gets its first REST API endpoint implemented. So start the server using `npm start` and visit: + +1. `http://localhost:8000/api/cat/1` – It responds with a 404 +2. `http://localhost:8000/api/cat/123` – It responds with a 200 and very little information about a cat diff --git a/package-lock.json b/package-lock.json index e7b14a6..5d870c3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,6 +14,15 @@ "@types/node": "*" } }, + "@types/compression": { + "version": "0.0.35", + "resolved": "https://registry.npmjs.org/@types/compression/-/compression-0.0.35.tgz", + "integrity": "sha512-SrHPmzvC5AL6cCrq0fDCU2AX9sOK/Azik2mdkbLhGpxOlzS7rTALjtdk/WzvKY3pQqEz3byvz1nnX/AmMk6X0Q==", + "dev": true, + "requires": { + "@types/express": "*" + } + }, "@types/connect": { "version": "3.4.32", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.32.tgz", @@ -23,6 +32,15 @@ "@types/node": "*" } }, + "@types/cookie-parser": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@types/cookie-parser/-/cookie-parser-1.4.1.tgz", + "integrity": "sha512-iJY6B3ZGufLiDf2OCAgiAAQuj1sMKC/wz/7XCEjZ+/MDuultfFJuSwrBKcLSmJ5iYApLzCCYBYJZs0Ws8GPmwA==", + "dev": true, + "requires": { + "@types/express": "*" + } + }, "@types/express": { "version": "4.11.1", "resolved": "https://registry.npmjs.org/@types/express/-/express-4.11.1.tgz", @@ -120,6 +138,35 @@ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=" }, + "compressible": { + "version": "2.0.16", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.16.tgz", + "integrity": "sha512-JQfEOdnI7dASwCuSPWIeVYwc/zMsu/+tRhoUvEfXz2gxOA2DNjmG5vhtFdBlhWPPGo+RdT9S3tgc/uH5qgDiiA==", + "requires": { + "mime-db": ">= 1.38.0 < 2" + } + }, + "compression": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.2.tgz", + "integrity": "sha1-qv+81qr4VLROuygDU9WtFlH1mmk=", + "requires": { + "accepts": "~1.3.4", + "bytes": "3.0.0", + "compressible": "~2.0.13", + "debug": "2.6.9", + "on-headers": "~1.0.1", + "safe-buffer": "5.1.1", + "vary": "~1.1.2" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", + "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" + } + } + }, "content-disposition": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", @@ -135,6 +182,15 @@ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=" }, + "cookie-parser": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.3.tgz", + "integrity": "sha1-D+MfoZ0AC5X0qt8fU/3CuKIDuqU=", + "requires": { + "cookie": "0.3.1", + "cookie-signature": "1.0.6" + } + }, "cookie-signature": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", @@ -256,6 +312,11 @@ "statuses": ">= 1.4.0 < 2" } }, + "http-status-codes": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/http-status-codes/-/http-status-codes-1.3.0.tgz", + "integrity": "sha1-nNDnE5F3PQZxtInUHLxQlKpBY7Y=" + }, "iconv-lite": { "version": "0.4.23", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", @@ -354,6 +415,11 @@ "ee-first": "1.1.1" } }, + "on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==" + }, "parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", diff --git a/package.json b/package.json index de5a6d6..053617b 100644 --- a/package.json +++ b/package.json @@ -13,10 +13,15 @@ "prettier:write": "prettier --write service/**/*.{ts,tsx,js,jsx}" }, "dependencies": { - "express": "^4.16.2" + "express": "^4.16.2", + "compression": "1.7.2", + "cookie-parser": "1.4.3", + "http-status-codes": "1.3.0" }, "devDependencies": { "@types/express": "4.11.1", + "@types/compression": "0.0.35", + "@types/cookie-parser": "1.4.1", "typescript": "3.1.1", "ts-node": "6.1.1", "prettier": "^1.9.2" diff --git a/service/Application.ts b/service/Application.ts index 3a1d880..2d615fe 100644 --- a/service/Application.ts +++ b/service/Application.ts @@ -1,4 +1,5 @@ import { ExpressServer } from './ExpressServer' +import { CatEndpoints } from './cats/CatEndpoints' /** * Wrapper around the Node process, ExpressServer abstraction and complex dependencies such as services that ExpressServer needs. @@ -6,7 +7,7 @@ import { ExpressServer } from './ExpressServer' */ export class Application { public static async createApplication() { - const expressServer = new ExpressServer() + const expressServer = new ExpressServer(new CatEndpoints()) await expressServer.setup(8000) Application.handleExit(expressServer) diff --git a/service/ExpressServer.ts b/service/ExpressServer.ts index d2cf3e1..ecde37a 100644 --- a/service/ExpressServer.ts +++ b/service/ExpressServer.ts @@ -1,6 +1,12 @@ import * as express from 'express' import { Express } from 'express' import { Server } from 'http' +import * as compress from 'compression' +import * as bodyParser from 'body-parser' +import * as cookieParser from 'cookie-parser' + +import { noCache } from './NoCacheMiddleware' +import { CatEndpoints } from './cats/CatEndpoints' /** * Abstraction around the raw Express.js server and Nodes' HTTP server. @@ -11,8 +17,14 @@ export class ExpressServer { private server?: Express private httpServer?: Server + constructor(private catEndpoints: CatEndpoints) { + } + public async setup(port: number) { const server = express() + this.setupStandardMiddlewares(server) + this.configureApiEndpoints(server) + this.httpServer = this.listen(server, port) this.server = server return this.server @@ -25,4 +37,14 @@ export class ExpressServer { public kill() { if (this.httpServer) this.httpServer.close() } + + private setupStandardMiddlewares(server: Express) { + server.use(bodyParser.json()) + server.use(cookieParser()) + server.use(compress()) + } + + private configureApiEndpoints(server: Express) { + server.get('/api/cat/:catId', noCache, this.catEndpoints.getCatDetails) + } } diff --git a/service/NoCacheMiddleware.ts b/service/NoCacheMiddleware.ts new file mode 100644 index 0000000..2a5ff70 --- /dev/null +++ b/service/NoCacheMiddleware.ts @@ -0,0 +1,8 @@ +import { Request, Response, NextFunction } from 'express' + +export function noCache(_: Request, res: Response, next: NextFunction) { + res.setHeader('Expires', '0') + res.setHeader('Pragma', 'no-cache') + res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate') + next() +} diff --git a/service/cats/CatEndpoints.ts b/service/cats/CatEndpoints.ts new file mode 100644 index 0000000..a5b9f5a --- /dev/null +++ b/service/cats/CatEndpoints.ts @@ -0,0 +1,21 @@ +import { NextFunction, Request, Response } from 'express' +import * as HttpStatus from 'http-status-codes' + +export class CatEndpoints { + public getCatDetails = async (req: Request, res: Response, next: NextFunction) => { + try { + // usually we will contact some service here and do some logic + const catId = req.params.catId + + if (catId >= 90) { + res.json({ catId, name: 'Some lovely Kitty' }) + } else { + res.sendStatus(HttpStatus.NOT_FOUND) + } + } catch (err) { + // something could fail unexpectedly... + // at some point the middleware chain should handle errors + next(err) + } + } +}