Nitro is a framework for building servers with javascript and typescript. It is my go to framework for building servers and APIs. It was developed as part of the Nuxt ecosystem.
In this article I will share some tips and tricks that I use to build servers with Nitro.
Health Checks
Health checks are a common way to check if a server is running. Although these are more of a common practice in microservices. You can also use them in a simple api to check if the server is running.
// routes/health.ts
export default defineEventHandler((event) => {
return true;
});
Documenting Your Nitro API
Nitro supports documenting your API. Every time I make an API I like to document it. This makes it easier for the frontend team to understand all available endpoints. It also makes it easier to test the API as a backend engineer.
Nitro supports OpenAPI documentation. OpenAPI is a specification for describing RESTful APIs. Nitro generates an OpenAPI json file that describes your API.
You can enable this by adding the following to your nitro.config.ts
file:
export default defineNitroConfig({
experimental: {
openapi: true,
},
});
By default this makes your API documentation available only during development, but you can also set it up to be available during production.
export default defineNitroConfig({
experimental: {
openapi: {
runtime: 'runtime', // or 'prerender'
},
},
});
Then you can start the server and navigate to the documentation at http://localhost:3000/_swagger/
. Or http://localhost:3000/_scalar/
to see the api documentation.
To actually specify more information about each endpoint (apart from the http method and path) you can use the compiler macro: defineRouteMeta
. For example to specify the possible query parameters of a route:
// routes/hello.ts
defineRouteMeta({
parameters: [
{
in: 'query',
name: 'name',
required: false,
description: 'The name of the person to say hello to',
schema: {
type: 'string',
},
},
],
});
export default defineEventHandler((event) => {
const name = getQuery(event, 'name');
return `Hello ${name}`;
});
Authentication Documentation
To specify the authentication requirements for your endpoint you need to do two steps:
- globally define the possible authentication methods (using the
defineRouteMeta
$global property) - specify the authentication requirements for each endpoint (using the
defineRouteMeta
macro)
Here I am using an example of an admin authentication using the X-API-Key
header.
// routes/hello.ts
defineRouteMeta({
openAPI: {
$global: {
components: {
securitySchemes: {
apiKeyAuth: {
type: 'apiKey',
in: 'header',
name: 'X-API-Key',
description: 'API key for admin authentication'
}
}
}
},
security: [
{
apiKeyAuth: []
}
],
},
});
export default defineEventHandler({
onRequest: [adminAuthentication],
handler: async (event: H3Event) => {
return 'Hello Admin';
}
});
Validating Requests
Using a Database
Authentication
Enabling CORS
Are you getting errors like this: 💢😵💫
Access to fetch at 'http://localhost:3000/api/hello' from origin 'http://localhost:3000' has been blocked by CORS policy: No
'Access-Control-Allow-Origin' header is present on the requested resource.
If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
Create a middleware/cors.ts
file, this will enable CORS for all routes.
export default defineEventHandler((event) => {
setResponseHeaders(event, {
'Access-Control-Allow-Methods': 'GET,HEAD,PUT,PATCH,POST,DELETE',
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Credentials': 'true',
'Access-Control-Allow-Headers': '*',
'Access-Control-Expose-Headers': '*'
});
if (event.method === 'OPTIONS') {
event.node.res.statusCode = 204;
event.node.res.statusMessage = 'No Content.';
return 'OK';
}
});
Testing
pnpm i -D vitest