Retes
Typed, Declarative, Data-Driven Routing for Node.js.
What is Retes?
Retes is a routing library for Node.js written in TypeScript and inspired by Clojure's Ring, Compojure and Retit. It is built directly on top of the Node.js http
module, so you can use it as an alternative to Express or Koa.
- Data-Driven: In Retes you define routes using the existing data structures. This way, we limit the number of abstractions and we are able to easily transform and combine routes. Our routing description is declarative.
- Typed: The type system conveniently helps us control the shape of our routing
- Battery-Included (wip): Most common middlewares will be included out of the box
Key Features
- built-in parsing of query params, body and route's dynamic segments
- built-in file uploading handling mechansim
- fast route matching (see Benchmarks)
- handlers are functions that take requests as input and return responses as output
- middlewares can be combined on per-route basis
- an HTTP response is just an object containing at least
statusCode
and body
keys
Why Retes?
- declarative route descriptions make them easily composable
- functional handlers are more natural fit for the HTTP flow
- common request/response transformations are already built-in
- typed routes make it easier to discover and control the shape of data flowing in and out
Usage
Generate a new Node.js project
mkdir my-api
cd my-api
npm init -y
Add retes
as a dependency
npm i retes
Create tsconfig.json
with the following content:
{
"compilerOptions": {
"lib": [ "es2015", "DOM" ]
}
}
Create app.ts
with the following content:
import { Route, ServerApp, Response } from 'retes';
const { GET, POST } = Route;
const { Created } = Response;
const routes = [
GET("/", () => "Hello, World"),
GET("/welcome/:name", ({ params }) => {
return { statusCode: 200, body: `Hello, ${params.name}` }
}),
POST("/user", ({ params: { name } }) => `Received: '${name}'`),
POST("/widget", ({ params: { name, count } }) => {
return Created()
})
]
async function main() {
const app = new ServerApp(routes);
await app.start(3000);
console.log('started')
}
main()
Save it to a file, e.g. app.ts
and run using ts-node
Install ts-node
globally
npm i -g ts-node
Run the application
ts-node app.ts
The server application listens on the specified port, in our case :3000
. Open localhost:3000 and test the routes.
Features
Params
Retes combines requests' query params, body params and segment params into params
.
import { Route, ServerApp, Response } from 'retes';
const { GET, POST } = Route;
const { OK } = Response;
const routes = [
GET("/query-params", ({ params }) => OK(params)),
POST("/body-form", ({ params }) => OK(params)),
POST("/body-json", () => OK(params)),
GET("/segment/:a/:b", ({ params }) => OK(params)),
]
async function main() {
const app = new ServerApp(routes);
await app.start(3000);
console.log('started')
}
main()
This GET
query
http :3000/query-params?a=1&b=2
returns
HTTP/1.1 200 OK
{
"a": "1",
"b": "2"
}
This POST
query with Content-Type
set to application/x-www-form-urlencoded; charset=utf-8
http --form :3000/body-form a:=1 b:=2
returns
HTTP/1.1 200 OK
{
"a": "1",
"b": "2"
}
This POST
query with Content-Type
set to application/json
http :3000/body-json a:=1 b:=2
returns
HTTP/1.1 200 OK
{
"a": 1,
"b": 2
}
This GET
request
http :3000/segment/1/2
returns
HTTP/1.1 200 OK
{
"a": "1",
"b": "2"
}
Convenience Wrappers for HTTP Responses
import { Route, ServerApp, Response } from 'retes';
const { GET } = Route;
const { Created, OK, Accepted, InternalServerError } = Response;
const routes = [
GET("/created", () => Created("payload")),
GET("/ok", () => OK("payload")),
GET("/accepted", () => Accepted("payload")),
GET("/internal-error", () => InternalServerError()),
]
async function main() {
const app = new ServerApp(routes);
await app.start(3000);
console.log('started')
}
main()
Middleware Composition on Per-Route Basis
import { Route, ServerApp } from 'retes';
const { GET } = Route;
const prepend = next => request => `prepend - ${next()}`;
const append = next => request => `${next()} - append`
const routes = [
GET("/middleware", () => "Hello, Middlewares", {
middleware: [prepend, append]
})
]
async function main() {
const app = new ServerApp(routes);
await app.start(3000);
console.log('started')
}
main()
Declarative Validation
Retes comes with a built-in validation middleware. It's a higher-order functions that checks the request parameters against a Zod schema. Note that request parameters combine query params, body params and segment params into a single values available as params
.
import { Route, ServerApp, Middleware } from 'retes';
import * as z from 'zod';
const { GET } = Route;
const schema = z.object({
name: z.string()
});
const routes = [
GET('/request-validation', ({ params }) => {
return `The request for this handler is validated using the given schema`,
}, { middleware: [ Middleware.Validation(schema) ] }))
]
async function main() {
const app = new ServerApp(routes);
await app.start(3000);
console.log('started')
}
main()
Benchmarks
WIP
Roadmap