Skip to main content

Routing

For an introduction, see Basic routing.

Routes are defined with methods of LunaticServer (and Router) class, for example app.get() for GET requests, app.post() for POST requests etc. You can also use method app.use() to handle all request methods.

These methods accepts route and handler. Route is a path of incoming request, and handler is a function that is called when request is handled. Here is very basic route example:

import { LunaticServer } from '@shelepuginivan/lunatic'

const app = new LunaticServer()

app.get('/route', (req, res) => {
res.status(200).end()
})

Methods

You can handle HTTP requests with methods on same path. The following code is an example of app that handles GET / and POST / requests:

app.get('/route', (req, res) => {
res.status(200).json({ message: 'GET request' })
})

app.post('/route', (req, res) => {
// You can use Status enum as shortcut
res.status(Status.Ok).json({ message: 'POST request' })
})
tip

You can use Status enum as shortcut:

res.status(Status.Ok).end()

You can use app.use() method to handle all HTTP methods:

app.use('/route', (req, res, next) => {
console.log("I don't know what the method was handled...")
next() // call next handler
})

Route paths

Route path is a string that defines to which path request can be made. With method, it defines an endpoint. Here are some examples of paths:

// Handles GET /
app.get('/', (req, res) => {
res.status(200).text('Hello!')
})

// Handles GET /some
app.get('/some', (req, res) => {
res.status(200).json({ message: '/some' })
})

// Handles GET /api/posts
app.get('/api/posts', (req, res) => {
const posts = getPostsSomehow()
res.status(200).json(posts)
})

Route path can be completely omitted, in this case handler function will handle every request with matching method:

app.get((_req, res, next) => {
res.setHeader('X-Handle-Time', Date.now())
next()
})

Dynamic paths

Lunatic supports dynamic route paths. The route matching system is custom, and it has similarities to Next.js:

  • * - matches all paths, except for an empty one
  • :<param_name> - matches a single part of request path
  • ...<params_arr> - matches multiple parts of request path

Here are some examples:

// Handles GET /:id
// e.g. /1, /b9e5e951-cc0e-4d6d-b659-a28fc7a74362 etc.
app.get('/:id', (req, res) => {
const id = req.params.id // get the id from req.params
res.status(200).text(id)
})

// Handles GET /...arr
// e.g /a/b/c/d, /a, /1/2 etc.
app.get('/...arr', (req, res) => {
res.status(200).json(req.params.arr)
})

// Handles GET /admin/*
// e.g. /admin/drop_database, /admin/sudo/rm_rf
// but NOT /admin, because `*` does not match the empty path
app.get('/admin/*', (_req, res) => {
res.status(501).end()
})

Path parameters

Path parameters can be accessed with req.params:

app.get('/docs/:topic', (req, res) => {
console.log(req.params) // E.g. for GET /docs/routing: { topic: 'routing' }
res.status(204).end()
})

app.get('/multiple/...parameters', (req, res) => {
console.log(req.params) // E.g. for GET /multiple/1/2/3/4/5: { parameters: ['1', '2', '3', '4', '5'] }
res.status(204).end()
})
note

Token * does not add any property in req.params.

Router

In addition to RequestHandler (i.e. a handler function as in examples above), request methods accepts Router. Router is a class of Lunatic framework which acts similarly a LunaticServer. In fact, LunaticServer extends Router with additional methods.

You can use router as in example below:

import { Router } from '@shelepuginivan/lunatic'

const myRouter = new Router()

myRouter.get('/', (_req, res) => {
res.status(200).text('Hello from Router!')
})

app.use('/router', router) // handle all requests to /router with created router

In this example, when request is coming to /router or /router/*, myRouter will handle this request.

note

In contrast to handler functions, Router handles requests whose paths begin with a specified prefix.

You can also nest routers in each other:

const router = new Router()
const innerRouter = new Router()

innerRouter.get('/some', (_req, res) => {
res.status(200).json({ message: 'Hello from inner router' })
})

router.use('/inner', innerRouter)
app.use('/router', router)

If none of router handlers matched the request, it will be passed in next app request handler.

Routers allow you to separate application logic so that each router is responsible for its own functionality. For example, there can be a router for posts, registration, and for an API.