Skip to main content

Middlewares

Middleware is a method that is called after a request has been received by the server and before a response is sent to that request. Essentially, every request handler (or Router) is a middleware itself.

Writing your own middleware

Here is example of middleware:

import { RequestHandler } from '@shelepuginivan/lunatic' // only for TypeScript

const myMiddleware: RequestHandler = (req, _res, next) => {
console.log(`Handled ${req.method} request at ${Date.now()}`)
next() // call next middleware
}

app.use(myMiddleware)

You can specify request method and path that certain middleware should handle (see Routing):

import { RequestHandler } from '@shelepuginivan/lunatic'

const blockTrace: RequestHandler = (_req, res) => {
res.status(405).json({ message: 'TRACE is not allowed' })
}

app.trace(blockTrace) // blocks all TRACE requests

Built-in middlewares

Lunatic comes bundled with commonly used middlewares "out of the box". You don't need to install any package, just import them.

bodyParser

bodyParser is a middleware allows you to retrieve the body of the request. It supports text/plain and application/json body types. Other content types, such as multipart/form-data will be ignored.

Body object can be accessed with req.body property.

import { bodyParser } from '@shelepuginivan/lunatic'

app.use(bodyParser)

app.post('/', (req, res) => {
console.log(req.body)
res.status(200).json({ message: 'received body' })
})

app.listen(8000)

cookieParser

cookieParser is a middleware allows you to retrieve request cookies:

import { cookieParser } from '@shelepuginivan/lunatic'

app.use(cookieParser)

app.post('/', (req, res) => {
console.log(req.cookies)
res.status(200).json({ message: 'received cookies' })
})

app.listen(8000)
note

If request had no Cookie HTTP header, field req.cookies will be undefined.

cors

cors is a middleware that allows CORS requests.

import { cors } from '@shelepuginivan/lunatic'

app.use(cors())

app.post('/', (req, res) => {
res.status(200).json({ message: 'now CORS is supported!' })
})

app.listen(8000)

cors is a configurable middleware, meaning that it accepts options object:

import { cors, CorsOptions } from '@shelepuginivan/lunatic'

const corsOptions: CorsOptions = {
origin: 'http://localhost:3000',
credentials: true,
}

app.use(cors(corsOptions))

app.post('/', (req, res) => {
res.status(200).json({ message: 'now CORS is supported!' })
})

app.listen(8000)

CORS options

tip

If you are using TypeScript, you can import interface CorsOptions:

import { CorsOptions } from '@shelepuginivan/lunatic'
  • origin - allowed request origin
    • boolean - set to true to enable any origin, false to disable CORS requests
    • string - sets a specific origin, e.g. http://localhost:3000
    • RegExp - allows CORS request for origins that matches this regular expression
    • string[] - allows CORS request for origins presented in array
    • RegExp[] - allows CORS request for origins, that match one of presented regular expressions
    • (origin: string) => boolean - predicate function, should return true if CORS is allowed, otherwise false
  • methods - allowed request methods
    • HttpMethod - specific HTTP method
    • HttpMethod[] - array of allowed HTTP methods
  • allowedHeaders - allowed request headers
    • string - specific header
    • string[] - array of allowed headers
  • credentials
    • boolean - allow or disallow credentials respectively. Note that if origin is set to * (as by default), credentials won't be included
  • exposedHeaders - response headers that can be accessed in browser JavaScript
    • string - specific header
    • string[] - array of exposed headers
  • maxAge - configures Access-Control-Max-Age header
    • number - (seconds) - Access-Control-Max-Age value
  • corsErrorStatus - response status if CORS is not allowed for this request. Default: 403
    • number - HTTP status code
  • preflightSuccessStatus - response status for preflight OPTIONS request. Default: 204
    • number - HTTP status code

formParser

formParser is a middleware that parses multipart/form-data. Other content types are ignored.

Text input values can be retrieved with req.body, and uploaded files with req.files.

import { formParser, UploadedFile } from '@shelepuginivan/lunatic'
import { writeFile } from 'fs/promises'

app.use(formParser)

app.post('/', async (req, res) => {
console.log(req.body)

const [uploadedFile] = (req.files as Record<'file', UploadedFile[]>).file

await writeFile('new.txt', uploadedFile.data)
await res.status(200).json({ message: 'received form data' })
})

app.listen(8000)

UploadedFile

UploadedFile is the interface where files are available for retrieval from a request. It has the following properties:

  • data: Buffer - content of uploaded file
  • filename: string - name of uploaded file
  • mimetype: string - MIME-type of uploaded file
note

Fields of req.files objects are always of type UploadedFile[], even if a single file has been uploaded. This is done to standardise form processing for different requests.

notFound

notFound is a small middleware that returns 404 Not Found status code if the request did not match any route.

import { notFound } from '@shelepuginivan/lunatic'

app.post('/', (req, res) => {
res.status(200).json({ message: 'this IS found' })
})

app.use(notFound) // should be the last to check all routes before sending 404

app.listen(8000)
info

By default, if no route matched the request, server will respond with 501 Not Implemented status.

serveStatic

serveStatic is a middleware that allows to send static files from specified directory.

import { serveStatic } from '@shelepuginivan/lunatic'
import { join } from 'path'

const staticDir = join(__dirname, 'static')

app.use('/static', serveStatic(staticDir))

app.listen(8000)

In this example, all requests to /static and /static/* will be handled by serveStatic. It will send a file like as follows:

  • Request path /static/style.css -> File <staticDir>/style.css

  • Request path /static/images/d430849b.png -> File <staticDir>/images/d430849b.png

etc.

If requested file does not exist, it will respond with 404 Not Found status.

serveStatic is a configurable middleware, meaning that it accepts options object:

import { serveStatic, ServeStaticOptions } from '@shelepuginivan/lunatic'
import { join } from 'path'

const serveStaticOptions: ServeStaticOptions = {
dotfiles: 'forbid',
index: 'index.html',
lastModified: false,
}

app.use('/static', serveStatic(join(__dirname, 'static'), serveStaticOptions))

app.listen(8000)

ServeStaticOptions

tip

If you are using TypeScript, you can import interface ServeStaticOptions:

import { ServeStaticOptions } from '@shelepuginivan/lunatic'
  • dotfiles - how should server respond when client attempts to get dotfile (file which name starts with ., e.g. .htaccess). Default: 'ignore'
    • 'allow' - respond with 200 OK status and send dotfile
    • 'forbid' - respond with 403 Forbidden status and not send dotfile
    • 'ignore' - respond with 404 Not Found status and not send dotfile, i.e. act like file does not exist
  • etag - allow or disable ETag header. Default: true
    • boolean - allow or disable ETag header respectively
  • index - how should server respond if request path is a directory. Default: 'index.html'.
    • string - certain filename that should be sent
    • false - set if you want to disable index files at all
  • lastModified - allow or disable Last-Modified header. Default: true
    • boolean - allow or disable Last-Modified header respectively
info

The value of the ETag header is set as a hash of the last modification time of the file