Skip to main content

Express

https://expressjs.com/

https://github.com/expressjs/express

Debug

https://expressjs.com/en/guide/debugging.html

To see the debug logs run:

DEBUG=express:* node main

Configuration

import path from 'path'

const app = express()

// Optional - we can instead do app.listen(3000).
// Then we can do app.get("port").
app.set('port', process.env.PORT || 3000)

// Parse JSON body in (eg POST, PUT and PATCH) requests with 'Content-Type: application/json',
// and make it available in req.body as JS object.
app.use(express.json())

// Parse URL encoded body in a <form> submission (Content-Type: application/x-www-form-urlencoded),
// and make it available in req.body as JS object.
app.use(express.urlencoded({ extended: true }))

app.use(express.static('public'))
app.use(express.static(path.join(__dirname, '../public')))
app.use(
express.static(path.join(__dirname, '../public'), { maxAge: 31557600000 })
)

app.listen(app.get('port'), () => {
console.log(
'Server is running on port %d in %s mode',
app.get('port'),
app.get('env')
)
})

Libraries

https://expressjs.com/en/resources/middleware.html

https://expressjs.com/en/resources/utils.html

Testing

CORS

Why doesn't adding CORS headers to an OPTIONS route allow browsers to access my API? - https://stackoverflow.com/questions/7067966/why-doesnt-adding-cors-headers-to-an-options-route-allow-browsers-to-access-my

https://github.com/troygoode/node-cors-server

https://enable-cors.org/server_expressjs.html

Manual

app.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', 'http://localhost:3000')
res.header('Access-Control-Allow-Headers', 'Content-Type')
res.header('Access-Control-Allow-Methods', 'GET,POST,PUT,DELETE')
next()
})

Using the cors package

https://github.com/expressjs/cors

If we don't pass any options:

import cors from 'cors'
app.use(cors())

It uses the default configuration which returns:

  • Access-Control-Allow-Origin: *
  • Access-Control-Allow-Methods: GET,HEAD,PUT,PATCH,POST,DELETE
  • It also sets Access-Control-Allow-Headers: content-type if the request has Access-Control-Request-Headers: content-type.

So with no options it accepts all origins! However we can specify the allowed origins:

app.use(
cors({
origin: ['https://example.com'],
})
)

We can also use a callback for origin - see https://github.com/expressjs/cors#configuring-cors-asynchronously

Error handling

https://expressjs.com/en/guide/error-handling.html

https://github.com/expressjs/express/blob/master/examples/error/index.js

https://scoutapm.com/blog/express-error-handling

https://strongloop.com/strongblog/async-error-handling-expressjs-es7-promises-generators/

Custom error handling middleware

https://expressjs.com/en/guide/using-middleware.html#middleware.error-handling

The 4 arguments must be provided

"Error-handling middleware always takes four arguments. You must provide four arguments to identify it as an error-handling middleware function. Even if you don’t need to use the next object, you must specify it to maintain the signature. Otherwise, the next object will be interpreted as regular middleware and will fail to handle errors." source

import { ErrorRequestHandler } from 'express'

/**
* Important: this error handler does not get invoked if the error is thrown in
* an `async` function or callback unless you wrap the code with a try-catch
* and call next(error).
*/
// Important: 'next' argument must be provided, even if unused! See why at
// https://expressjs.com/en/guide/using-middleware.html#middleware.error-handling
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const errorHandler: ErrorRequestHandler = (err, req, res, next) => {
console.error('Error handler', err)
res.sendStatus(500)
}

router.use(errorHandler)
caution

In Express 4 error handlers are only called from handlers that are synchronous (ie not async functions nor callbacks). In Express 5 is OK.

// Synchronous -> the error handler works :)
// The server does not crash. There's a response sent to the client (500 Internal Server Error).
router.get('/sync', (req, res) => {
const a = {}
// @ts-ignore
a.thiswill.createacrash // TypeError: Cannot read properties of undefined (reading 'createacrash')
})

// Asynchronous -> the error handler does NOT work :/
// The server crashes and it stops. There's no response sent to the client.
router.get('/async', async (req, res) => {
const a = {}
// @ts-ignore
a.thiswill.createacrash
})
router.get('/callback', (req, res) => {
setTimeout(() => {
const a = {}
// @ts-ignore
a.thiswill.createacrash
}, 100)
})

In asynchronous handlers we must catch the errors in a try-catch and then call next(error) if we want our error handling middleware to be called.

Alternatively we can use https://www.npmjs.com/package/express-async-handler.

TypeScript

New project setup with TypeScript

git init
touch .gitignore # Add node_modules
npm init # Creates packages.json. The "main" script (entry point) should be build/index.js
npm i [-E] express
npm i -D [-E] typescript @types/express ts-node-dev
npx tsc --init # Creates tsconfig.json

Tweak tsconfig.json:

{
"compilerOptions": {
"outDir": "./build",
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
}
}

Add scripts to package.json:

{
"main": "build/index.js",
"scripts": {
"start": "node build/index.js",
"dev": "ts-node-dev src/index.ts",
"build": "npx tsc"
}
}

See this to setup ESLint.

TypeScript project setup examples

Extending the Request type to add fields to it

Extend Express Request object using Typescript - https://stackoverflow.com/questions/37377731/extend-express-request-object-using-typescript/40762463

Add the following to any .ts file:

declare module 'express-serve-static-core' {
interface Request {
user?: User
}
}

Alternatively, we can also create a file types/express/index.d.ts with the same content, and then set "typeRoots": ["./types"] at tsconfig.json.

Typing the params, request and response

https://www.jonmellman.com/posts/typescript-for-api-contracts - https://github.com/jonmellman/blog-examples/tree/master/typescript-for-api-contracts

const updateUserEmail: RequestHandler<
{ userId: string }, // Params
{ user: User } | { error: string }, // Response Body
{ email: string } // Request Body
> = async (req, res) => {}

Service layer

Passport

https://github.com/cypress-io/cypress-realworld-app/blob/develop/backend/auth.ts

JWT

https://github.com/auth0/node-jsonwebtoken/

Note that there's no performance difference between the sync and async versions of jwt.sign and jwt.verify according to https://github.com/auth0/node-jsonwebtoken/issues/566.

https://github.com/auth0/express-jwt

Curso de Backend con Node.js: Autenticación con Passport.js y JWT - https://platzi.com/cursos/passport - https://github.com/platzi/curso-nodejs-auth