# Introduction
[](https://www.npmjs.com/package/restana)
[](https://www.npmjs.com/package/restana)
[](https://www.npmjs.com/package/restana)
[](https://www.npmjs.com/package/restana)
[](https://github.com/BackendStack21/restana)
Restana is a lightweight and fast Node.js framework for building RESTful APIs. Inspired by Express, it provides a simple and intuitive API for routing, handling requests and responses, and middleware management. It is designed to be easy to use and integrate with other Node.js modules, allowing developers to quickly build scalable and maintainable APIs.

> Check it yourself: https://web-frameworks-benchmark.netlify.app/result?f=feathersjs,0http,koa,nestjs-express,express,sails,nestjs-fastify,restana,fastify
### Express.js comparison (in the eyes of ChatGPT/OpenAI)
Restana and Express are both popular web frameworks for building REST APIs in Node.js.
#### Performance
- Restana is a lightweight framework, offering faster performance than Express.
- Express is more feature-rich and can handle complex applications, but this added functionality comes with a slight performance trade-off.
#### Speed of Development
- Express has a larger community and more resources available, making it easier to find answers to development questions.
- Restana is more straightforward and has a smaller learning curve, allowing for faster development of simple APIs.
#### Features
- Express has a wide range of built-in features, such as routing, middleware, and template engines.
- Restana focuses on simplicity and speed, offering basic features such as routing and middleware.
Ultimately, the choice between Restana and Express will depend on the specific requirements of a project.
# Usage
Install
```bash
npm i restana
```
Create an HTTP API service:
```js
const restana = require('restana')
const service = restana()
service.get('/hi', (req, res) => res.send('Hello World!'))
service.start(3000);
```
Creating secure API service:
```js
const https = require('https')
const restana = require('restana')
const service = restana({
server: https.createServer({
key: keys.serviceKey,
cert: keys.certificate
})
})
service.get('/hi', (req, res) => res.send('Hello World!'))
service.start(3000);
```
Using `http.createServer()`:
```js
const http = require('http')
const restana = require('restana')
const service = restana()
service.get('/hi', (req, res) => res.send('Hello World!'))
http.createServer(service).listen(3000, '0.0.0.0')
```
> Please take note that when using `http.createServer()` the `service.close()` feature is not available, since restana does not have access to http server instance.
Optionally, learn through examples:
* [HTTPS service demo](demos/https-service.js)
* [HTTP2 service demo](demos/http2-service.js)
# Configuration options
- `server`: Allows to optionally override the HTTP server instance to be used.
- `prioRequestsProcessing`: If `TRUE`, HTTP requests processing/handling is prioritized using `setImmediate`. Default value: `TRUE`
- `defaultRoute`: Optional route handler when no route match occurs. Default value: `((req, res) => res.send(404))`
- `errorHandler`: Optional global error handler function. Default value: `(err, req, res) => res.send(err)`. Values that are not `Error` instances are wrapped first. The response body is `{ code, message }`, where `code` is the first valid HTTP status code found in `err.status`, `err.code` or `err.statusCode` (falls back to `500`), and `message` is the matching status text (e.g. `Not Found`). Error messages and `err.data` are only exposed with `debugErrors: true` outside production, which prevents leaking internal details such as database connection strings, file paths or stack traces.
- `router`: The [0http](https://github.com/BackendStack21/0http) router implementation, `'trie'` or `'sequential'`. Default value: `'trie'`. See [Routers](#routers).
- `caseSensitive`: Trie router only. When `FALSE`, static path segments match case-insensitively (ASCII letters only). Default value: `TRUE`
- `ignoreTrailingSlash`: Trie router only. When `TRUE`, `/users/` and `/users` are the same path. Default value: `TRUE`
- `routerCacheSize`: Sequential router only. The router matching cache size, indicates how many request matches will be kept in memory. Default value: `2000`
- `enableTrace`: When `TRUE`, the `TRACE` HTTP method handler is available for debugging purposes. Default value: `FALSE`. ⚠️ Not recommended for production deployments.
- `securityHeaders`: When `TRUE`, default security headers are set on every response. Set to `FALSE` to disable (e.g. when using Helmet or serving non-browser clients). Default value: `TRUE`.
- `trustProxy`: When `TRUE`, trust the first `X-Forwarded-Proto` value when deciding whether to send HSTS. Only enable this behind a reverse proxy that replaces forwarded headers. Default value: `FALSE`.
- `debugErrors`: When `TRUE`, `res.send(error)` and the default `errorHandler` include `error.message` and `error.data` outside production. Use only for local debugging. Default value: `FALSE`.
### Routers
The dependency-free trie router is the default: static routes resolve in constant time and dynamic routes stay fast as the route count grows, without a match cache to size. The previous default, the sequential router, remains available:
```js
const service = restana({ router: 'sequential' })
```
The trie router intentionally differs from the sequential router:
- Paths are case-sensitive by default (`/Admin` does not match `/admin`); use `caseSensitive: false` to restore case-insensitive matching.
- A `*` that is not the last segment, or a `?` that is not the last character of a param segment, throws at registration.
- An absent optional param is not present on `req.params` (instead of `undefined`).
- A trailing `/*` also matches the bare prefix, with `req.params['*']` equal to `''`.
- Empty route segments are ignored (`/a//b` is `/a/b`).
- A request path that does not start with `/` matches nothing.
### Security defaults (v6.0+)
Restana now ships with these security hardening measures enabled by default:
- **Header injection protection**: Security-sensitive and hop-by-hop headers are blocked from the `res.send()` headers parameter.
- **Error masking**: `res.send(err)` masks the error message and strips `err.data` by default. Local development can opt in with `debugErrors: true`; production always masks details.
- **Default security headers**: `X-Content-Type-Options: nosniff`, `X-Frame-Options: DENY`, `X-XSS-Protection: 0`, and `Strict-Transport-Security` (on direct HTTPS or a trusted HTTPS proxy) are set on responses. Disable with `securityHeaders: false`.
- **TRACE method disabled by default**: Eliminates Cross-Site Tracing attack surface. Re-enable for debugging via `enableTrace: true` (not recommended in production).
- **Deep frozen config**: `getConfigOptions()` now freezes nested plain objects, not just the top-level copy.
# Full service example
```js
const bodyParser = require('body-parser')
const restana = require('restana')
const service = restana()
service.use(bodyParser.json())
const pets = // ...
service
.get('/pets/:id', async (req, res) => {
res.send(await pets.findOne(req.params.id))
})
.get('/pets', async (req, res) => {
res.send(await pets.find())
})
.delete('/pets/:id', async (req, res) => {
res.send(await pets.destroy(req.params.id))
})
.post('/pets/:name/:age', async (req, res) => {
res.send(await pets.create(req.params))
})
.patch('/pets/:id', async (req, res) => {
res.send(await pets.update(req.params.id, req.body))
})
service.start(3000)
```
# API and features
## Supported HTTP methods:
```js
const methods = ['get', 'delete', 'put', 'patch', 'post', 'head', 'options']
```
> ⚠️ `TRACE` is disabled by default in v6.0.0 to reduce attack surface (Cross-Site Tracing risk). Re-enable explicitly for debugging with `enableTrace: true` in the service constructor:
> ```js
> const service = restana({ enableTrace: true })
> service.trace('/debug', (req, res) => res.send('Echo: ' + req.url))
> ```
### Using .all routes registration
You can also register a route handler for `all` supported HTTP methods:
```js
service.all('/allmethodsroute', (req, res) => {
res.send(200)
})
```
## Starting the service
```js
service.start(3000).then((server) => {})
```
## Stopping the service
```js
service.close().then(()=> {})
```
## Accessing configuration options
```js
const opts = service.getConfigOptions()
```
> `getConfigOptions()` returns an isolated configuration snapshot. Plain objects and arrays are recursively cloned and frozen, preventing third-party middleware from modifying internal framework options. The `server` and other custom class instances remain live references and should not contain secrets.
## Async / Await support
```js
service.post('/star/:username', async (req, res) => {
await starService.star(req.params.username)
const stars = await starService.count(req.params.username)
res.send({ stars })
})
```
## Sending custom headers
```js
res.send('Hello World', 200, {
'x-response-time': 100,
vary: ['accept', 'origin']
})
```
> ⚠️ Security-sensitive and hop-by-hop headers are blocked from the `headers` parameter for security reasons:
> `transfer-encoding`, `content-length`, `connection`, `keep-alive`, `host`, `set-cookie`. Use `res.setHeader()` explicitly if you need to set these.
## The "res.send" method
Same as in express, for `restana` we have implemented a handy `send` method that extends
every `res` object.
Supported datatypes are:
- null
- undefined
- String
- Buffer
- Object
- Stream (errors on the stream are handled gracefully, terminating the response instead of leaving the connection hanging)
- Promise (recursive promise resolution is capped at a depth of 3 to prevent event loop starvation)
Boolean payloads are serialized as JSON. A number passed as the first argument remains the shorthand for an HTTP status code.
Example usage:
```js
service.get('/promise', (req, res) => {
res.send(Promise.resolve('I am a Promise object!'))
})
```
### The send method signature
```js
res.send(
// data payload
'Hello World',
// response code (default 200)
200,
// optional response headers (default NULL)
{
'x-cache-timeout': '5 minutes'
},
// optional res.end callback
err => { /*...*/ }
)
```
> Optionally, you can also just send a response code:
> `res.send(401)`
> An `undefined` body keeps the explicit status code: `res.send(undefined, 204)`
## Global error handling
By default, restana responds with the standard status text (e.g. `{ "code": 404, "message": "Not Found" }`), preventing internal details from being leaked. The HTTP status code is preserved from the first valid status code found in `err.status`, `err.code`, or `err.statusCode` (defaults to `500`). Non-numeric values such as Node.js system error codes (`'ENOENT'`) are ignored.
To customize error responses, provide your own `errorHandler`:
```js
const service = require('restana')({
errorHandler (err, req, res) {
console.log(`Something was wrong: ${err.message || err}`)
res.send(err)
}
})
service.get('/throw', (req, res) => {
throw new Error('Upps!')
})
```
> **Note:** `res.send(err)` masks the error's `message` and `data` by default. Set `debugErrors: true` only for local development when detailed responses are required. Production mode always masks details.
### errorHandler not being called?
> Issue: https://github.com/jkyberneees/ana/issues/81
Some middlewares don't call `return next()` inside a synchronous flow. In restana we enable async errors handling by default, however this mechanism fails when a subsequent middleware is just calling `next()` inside a sync or async flow.
Known incompatible middlewares:
- body-parser (https://www.npmjs.com/package/body-parser)
How to bring async chain compatibility to existing middlewares? The `body-parser` example:
```js
const jsonParser = require('body-parser').json()
const service = require('restana')()
service.use((req, res, next) => {
return new Promise(resolve => {
jsonParser(req, res, (err) => {
return resolve(next(err))
})
})
})
```
## Middlewares
### Global middlewares
```js
const service = require('restana')()
service.use((req, res, next) => {
// do something
return next()
});
...
```
### Prefix middlewares
```js
const service = require('restana')()
service.use('/admin', (req, res, next) => {
// do something
return next()
});
...
```
Multiple middlewares can be registered in a single call, with or without prefix:
```js
service.use(cors, logger)
service.use('/admin', authenticate, authorize)
```
### Route level middlewares
Connecting middlewares to specific routes is also supported:
```js
const service = require('restana')()
service.get('/admin', (req, res, next) => {
// do something
return next()
}, (req, res) => {
res.send('admin data')
});
...
```
As well, multiple middleware callbacks are supported:
```js
const service = require('restana')()
const cb0 = (req, res, next) => {
// do something
return next()
}
const cb1 = (req, res, next) => {
// do something
return next()
}
service.get('/test/:id', [cb0, cb1], (req, res) => {
res.send({ id: req.params.id })
})
```
### Async middlewares
Since version `v3.3.x`, you can also use async middlewares as described below:
```js
service.use(async (req, res, next) => {
await next()
console.log('All middlewares and route handler executed!')
}))
service.use(logging())
service.use(jwt())
...
```
In the same way you can also capture uncaught exceptions inside the request processing flow:
```js
service.use(async (req, res, next) => {
try {
await next()
} catch (err) {
console.log('upps, something just happened')
res.send(err)
}
})
service.use(logging())
service.use(jwt())
```
## Nested routers
Nested routers are supported as well:
```js
const service = require('restana')()
const nestedRouter = service.newRouter()
nestedRouter.get('/hello', (req, res) => {
res.send('Hello World!')
})
service.use('/v1', nestedRouter)
...
```
In this example the router routes will be available under `/v1` prefix. For example: `GET /v1/hello`
## Third party middlewares support:
> All middlewares using the `function (req, res, next)` signature format are compatible with restana.
Examples :
* **raw-body**: [https://www.npmjs.com/package/raw-body](https://www.npmjs.com/package/raw-body). See demo: [raw-body.js](demos/raw-body.js)
* **express-jwt**: [https://www.npmjs.com/package/express-jwt](https://www.npmjs.com/package/express-jwt). See demo: [express-jwt.js](demos/express-jwt.js)
* **body-parser**: [https://www.npmjs.com/package/body-parser](https://www.npmjs.com/package/body-parser). See demo: [body-parser.js](demos/body-parser.js)
## Service Events
Service events are accessible through the `service.events` object, an instance of https://nodejs.org/api/events.html
### Available events
- `service.events.BEFORE_ROUTE_REGISTER`: This event is triggered before registering a route.
## TypeScript
Type declarations are bundled. Route parameters are inferred from the path pattern:
```ts
import restana = require('restana')
const service = restana()
service.get('/users/:userId/posts/:postId', (req, res) => {
// req.params.userId and req.params.postId are typed as string
res.send({ user: req.params.userId, post: req.params.postId })
})
```
Optional params (`/:id?`), extensions (`/:name.json`) and wildcards (`/*`, available as `req.params['*']`) are supported. Other keys, such as params inherited from a nested router prefix, remain accessible as `string`. Use `restana.RouteParams<'/users/:id'>` to type handlers declared separately:
```ts
const getUser: restana.RequestHandler> = (req, res) => {
res.send({ id: req.params.id })
}
service.get('/users/:id', getUser)
```
## AWS Serverless Integration
`restana` is compatible with the [serverless-http](https://github.com/dougmoscrop/serverless-http) library, so restana based services can also run as AWS lambdas 🚀
```js
// required dependencies
const serverless = require('serverless-http')
const restana = require('restana')
// creating service
const service = restana()
service.get('/hello', (req, res) => {
res.send('Hello World!')
})
// lambda integration
const handler = serverless(service)
module.exports.handler = async (event, context) => {
return await handler(event, context)
}
```
> See also:
> Running restana service as a lambda using AWS SAM at https://github.com/jkyberneees/restana-serverless
## Cloud Functions for Firebase Integration
`restana` restana based services can also run as Cloud Functions for Firebase 🚀
```js
// required dependencies
const functions = require("firebase-functions");
const restana = require('restana')
// creating service
const service = restana()
service.get('/hello', (req, res) => {
res.send('Hello World!')
})
// lambda integration
exports = module.exports = functions.https.onRequest(service.callback())
```
## Serving static files
You can read more about serving static files with restana in this link:
https://itnext.io/restana-static-serving-the-frontend-with-node-js-beyond-nginx-e45fdb2e49cb
Also, the `restana-static` project simplifies the serving of static files using restana and docker containers:
- https://github.com/jkyberneees/restana-static
- https://hub.docker.com/r/kyberneees/restana-static
## Swagger/OpenAPI integration
- https://www.npmjs.com/package/restana-swagger-validator
## Third party integrations
```js
// ...
const service = restana()
service.get('/hello', (req, res) => {
res.send('Hello World!')
})
// using "the callback integrator" middleware
const server = http.createServer(service.callback())
//...
```
## Application Performance Monitoring (APM)
As a Node.js framework implementation based on the standard `http` module, `restana` benefits from out of the box instrumentation on
existing APM agents such as:
- https://www.npmjs.com/package/newrelic
- https://www.npmjs.com/package/elastic-apm-node
### Elastic APM - Routes Naming
"Routes Naming" discovery is not supported out of the box by the Elastic APM agent, therefore we have created our custom integration.
```js
// getting the Elastic APM agent
const agent = require('elastic-apm-node').start({
secretToken: process.env.APM_SECRET_TOKEN,
serverUrl: process.env.APM_SERVER_URL
})
// creating a restana application
const service = require('restana')()
// getting restana APM routes naming plugin
const apm = require('restana/libs/elastic-apm')
// attach route naming instrumentation before registering service routes
apm({ agent }).patch(service)
// register your routes or middlewares
service.get('/hello', (req, res) => {
res.send('Hello World!')
})
// ...
```
### New Relic - Routes Naming
"Routes Naming" discovery is not supported out of the box by the New Relic APM agent, therefore we have created our custom integration.
```js
// getting the New Relic APM agent
const agent = require('newrelic')
// creating a restana application
const service = require('restana')()
// getting restana APM routes naming plugin
const apm = require('restana/libs/newrelic-apm')
// attach route naming instrumentation before registering service routes
apm({ agent }).patch(service)
// register your routes or middlewares
service.get('/hello', (req, res) => {
res.send('Hello World!')
})
// ...
```
# Using this project? Let us know 🚀
https://goo.gl/forms/qlBwrf5raqfQwteH3
# Breaking changes
## 7.0
> Restana 7.0 makes the trie router the default, gives errors consistent responses, and tightens the public API. Read the full [migration guide](../MIGRATION.md).
Changed:
- The 0http trie router is the default. Paths are case-sensitive and route patterns are validated when registered. Use `caseSensitive: false` or `router: 'sequential'` for the previous behavior.
- `routerCacheSize` only applies to the sequential router.
- The default `errorHandler` responds the same way as `res.send(err)`: `404 Not Found` instead of `404 Internal Server Error`, and `debugErrors` applies to it.
- `res.send(number)` treats only integers from 100 to 999 as status codes. Other numbers are sent as JSON.
- Only `restana`, `restana/libs/elastic-apm`, `restana/libs/newrelic-apm` and `restana/package.json` can be imported.
- TypeScript: route registration and `use()` return the object they were called on. `getConfigOptions()` is `Readonly`.
## 6.1
> Restana 6.1 improves lifecycle reliability, secure defaults, performance tooling, and TypeScript support.
Added:
- `trustProxy` explicitly enables forwarded-protocol handling for HSTS.
- `debugErrors` explicitly enables detailed local error responses.
- TypeScript coverage for lifecycle, events, callback integration, and TRACE opt-in.
- Reproducible installs and performance smoke checks in CI.
Changed:
- `start()` now rejects on listen errors such as `EADDRINUSE`.
- Error details are masked by default in every environment and remain masked in production.
- Boolean response bodies are serialized as JSON; array-valued response headers are supported.
- `routerCacheSize: 0` now correctly disables route caching.
- Configuration snapshots recursively clone and freeze arrays and support circular plain objects.
- Forwarded protocol headers are ignored unless `trustProxy: true` is configured.
- Connection-specific, proxy-authentication, upgrade, and cookie headers are blocked from the `res.send()` header map.
Removed:
- The obsolete `disableResponseEvent` example and install-time survey.
- Legacy Travis CI configuration and the broken low-level performance demo.
## 6.0
> Restana version 6.0 focuses on security hardening and reducing attack surface.
Added:
- Minimum Node.js version is now v24.x (current LTS).
Changed:
- `TRACE` HTTP method is no longer supported by default. Removed from `methods.js` to prevent Cross-Site Tracing (XST) risks. Re-enable for debugging with `{ enableTrace: true }` in the service constructor.
- `res.send(data, code, headers)` now validates the `headers` parameter. Security-sensitive and hop-by-hop headers (`transfer-encoding`, `content-length`, `connection`, `keep-alive`, `host`, `set-cookie`) are silently dropped. Invalid header key characters (CRLF, newlines) are caught and skipped instead of crashing with a 500 error.
- `parseErr()` now respects `NODE_ENV`. In `production`, `res.send(err)` masks `err.message` and strips `err.data` to prevent internal details from leaking. In other environments, behavior is unchanged.
- `getConfigOptions()` now deep freezes nested plain objects in addition to the top-level freeze. The `server` reference is a live object and is excluded from freezing.
- Default security headers are now set on every response: `X-Content-Type-Options: nosniff`, `X-Frame-Options: DENY`, `X-XSS-Protection: 0`. `Strict-Transport-Security` is set automatically when TLS is detected (via `req.socket.encrypted` or `x-forwarded-proto: https` header). Disable with `{ securityHeaders: false }`.
- New `securityHeaders` option allows disabling default security headers entirely (default: `true`).
## 5.2
> Restana version 5.2 includes important security hardening while remaining backward compatible for most users.
Changed:
- The default `errorHandler` no longer sends `err.message` or `err.data` to clients. It now returns a generic `{ code, message: 'Internal Server Error' }` response. If you need the previous behavior, provide a custom `errorHandler`.
- `getConfigOptions()` now returns a frozen shallow copy of the options object instead of a direct mutable reference.
- Stream responses (`res.send(stream)`) now handle stream errors gracefully, terminating the response instead of leaving the connection hanging.
- Promise resolution in `res.send()` is now capped at a depth of 3 to prevent event loop starvation from deeply nested promise chains.
## 4.x
> Restana version 4.x is much more simple to maintain, mature and faster!
Added:
- Node.js v10.x+ is required.
- `0http` sequential router is now the default and only HTTP router.
- Overall middlewares support was improved.
- Nested routers are now supported.
- Improved error handler through async middlewares.
- New `getRouter` and `newRouter` methods are added for accesing default and nested routers.
Removed:
- The `response` event was removed.
- `find-my-way` router is replaced by `0http` sequential router.
- Returning result inside async handler is not allowed anymore. Use `res.send...`
## 3.x
Removed:
- Support for `turbo-http` library was dropped.
# Media
- restana = faster and efficient Node.js REST APIs: https://itnext.io/restana-faster-and-efficient-node-js-rest-apis-1ee5285ce66
# Support / Donate 💚
You can support the maintenance of this project:
- PayPal: https://www.paypal.me/kyberneees
- [TRON](https://www.binance.com/en/buy-TRON) Wallet: `TJ5Bbf9v4kpptnRsePXYDvnYcYrS5Tyxus`