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' })
})
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()
})
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.
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.