MacMusic  |  PcMusic  |  440 Software  |  440 Forums  |  440TV  |  Zicos
breed
Search

The complete guide to Node.js frameworks

Wednesday December 3, 2025. 10:00 AM , from InfoWorld
Node.js is one of the most popular server-side platforms, especially for web applications. It gives you non-blocking JavaScript without a browser, plus an enormous ecosystem. That ecosystem is one of Node’s chief strengths, making it a go-to option for server development.

This article is a quick tour of the most popular web frameworks for server development on Node.js. We’ll look at minimalist tools like Express.js, batteries-included frameworks like Nest.js, and full-stack frameworks like Next.js. You’ll get an overview of the frameworks and a taste of what it’s like to write a simple server application in each one.

Minimalist web frameworks

When it comes to Node web frameworks, minimalist doesn’t mean limited. Instead, these frameworks provide the essential features required to do the job for which they are intended. The frameworks in this list also tend to be highly extensible, so you can customize them as needed. With minimalist frameworks, pluggable extensibility is the name of the game.

Express.js

At over 47 million weekly downloads on npm, Express is one of the most-installed software packages of all time—and for good reason. Express gives you basic web endpoint routing and request-and-response handling inside an extensible framework that is easy to understand. Most other frameworks in this category have adopted the basic style of describing a route from Express. This framework is the obvious choice when you simply need to create some routes for HTTP, and you don’t mind a DIY approach for anything extra.

Despite its simplicity, Express is fully-featured when it comes to things like route parameters and request handling. Here is a simple Express endpoint that returns a dog breed based on an ID:

import express from 'express';

const app = express();
const port = 3000;

// In-memory array of dog breeds
const dogBreeds = [
'Shih Tzu',
'Great Pyrenees',
'Tibetan Mastiff',
'Australian Shepherd'
];
app.get('/dogs/:id', (req, res) => {
// Convert the id from a string to an integer
const id = parseInt(req.params.id, 10);

// Check if the id is a valid number and within the array bounds
if (id >= 0 && id < dogBreeds.length) {
// If valid, return the breed with a 200 OK status
res.status(200).json({ breed: dogBreeds[id] });
} else {
// If invalid, return an error message with a 404 Not Found status
res.status(404).json({ error: 'Dog breed not found' });
}
});

app.listen(port, () => {
console.log(`Server running at
});

You can easily see how the route is defined here: a string representation of a URL, followed by a function that receives a request and response object. The process of creating the server and listening on a port is simple.

If you are coming from a framework like Next, the biggest thing you might notice about Express is that it lacks a file-system based router. On the other hand, it offers a huge range of middleware plugins to help with essential functions like security.

Koa

Koa was created by the original creators of Espress, who took the lessons learned from that project and used them for a fresh take on the JavaScript server. Koa’s focus is providing a minimalist core engine. It uses async/await functions for middleware rather than chaining with next() calls. This can give you a cleaner server, especially when there are many plugins. It also makes the error handling less clunky for middleware.

Koa also differs from Express by exposing a unified context object instead of separate request and response objects, which makes for a somewhat less cluttered API. Here is how Koa manages the same route we created in Express:

router.get('/dogs/:id', (ctx) => {
const id = parseInt(ctx.params.id, 10);

if (id >= 0 && id < dogBreeds.length) {
ctx.status = 200;
ctx.body = { breed: dogBreeds[id] };
} else {
ctx.status = 404;
ctx.body = { error: 'Dog breed not found' };
}
});

The only real difference is the combined context object.

Koa’s middleware mechanism is also worth a look. Here’s a simple logging plugin in Koa:

const logger = async (ctx, next) => {
await next(); // This passes control to the router
console.log(`${ctx.method} ${ctx.url} - ${ctx.status}`);
};

// Use the logger middleware for all requests
app.use(logger);

Fastify

Fastify lets you define schemas for your APIs. This is an up-front, formal mechanism for describing what the server supports:

const schema = {
params: {
type: 'object',
properties: {
id: { type: 'integer' }
}
},
response: {
200: {
type: 'object',
properties: {
breed: { type: 'string' }
}
},
404: {
type: 'object',
properties: {
error: { type: 'string' }
}
}
}
};

fastify.get('/dogs/:id', { schema }, (request, reply) => {
const id = request.params.id;

if (id >= 0 && id < dogBreeds.length) {
reply.code(200).send({ breed: dogBreeds[id] });
} else {
reply.code(404).send({ error: 'Dog breed not found' });
}
});

fastify.listen({ port: 3000 }, (err, address) => {
if (err) {
fastify.log.error(err);
process.exit(1);
}
console.log(`Server running at ${address}`);
});

From this example, you can see the actual endpoint definition is similar to Express and Koa, but we define a schema for the API. The schema is not strictly necessary; it is possible to define endpoints without it. In that case, Fastify behaves much like Express, but with superior performance.

Hono

Hono emphasizes simplicity. You can define a server and endpoint with as little as:

const app = new Hono()
app.get('/', (c) => c.text('Hello, Infoworld!'))

And here’s how our dog breed example looks:

app.get('/dogs/:id', (c) => {
// Get the id parameter from the request URL
const id = parseInt(c.req.param('id'), 10);

// Check if the id is a valid number and within the array bounds
if (id >= 0 && id < dogBreeds.length) {
// Return a JSON response with a 200 OK status (default)
return c.json({ breed: dogBreeds[id] });
} else {
// Set status to 404 and return a JSON error message
c.status(404);
return c.json({ error: 'Dog breed not found' });
}
});

As you can see, Hono provides a unified context object, similar to Koa.

Nitro.js

Nitro is the back end for several full-stack frameworks, including Nuxt.js. As part of the UnJS ecosystem, Nitro goes further than Express in providing cloud-native tooling support. It includes a universal storage adapter and deployment support for serverless and cloud deployment targets.

Also see: Intro to Nitro: The server engine built for modern JavaScript.

Like Next.js, Nitro uses filesystem-based routing, so our Dog Finder API would exist at the following filepath:

/api/dogs/:id

The handler might look like this:

export default defineEventHandler((event) => {
// Get the dynamic parameter from the event context
const { id } = getRouterParams(event);
const parsedId = parseInt(id, 10);

// Check if the id is a valid number and within the array bounds
if (parsedId >= 0 && parsedId < dogBreeds.length) {
// Nitro handles JSON serialization
return { breed: dogBreeds[parsedId] };
} else {
setResponseStatus(event, 404);
return { error: 'Dog breed not found' };
}
});

Nitro inhabits the middle ground between a pure tool like Express and a full-blown stack, which is why full-stack front ends often use Nitro on the back end.

Batteries-included frameworks

Although Express and other minimalist frameworks set the standard for simplicity, more opinionated frameworks can be useful if you want additional features out of the box.

Nest.js

Nest is a progressive framework built with TypeScript from the ground up. Nest is actually a layer on top of Express (or Fastify), with additional services. It is inspired by Angular and incorporates the kind of architectural support found there. In particular, it includes dependency injection. Nest also uses annotated controllers for endpoints.

Also see: Intro to Nest.js: Server-side JavaScript development on Node.

Here is an example of injecting a dog finder provider into a controller:

// The provider:
import { Injectable, NotFoundException } from '@nestjs/common';

// The @Injectable() decorator marks this class as a provider.
@Injectable()
export class DogsService {
private readonly dogBreeds = [
'Shih Tzu',
'Great Pyrenees',
'Tibetan Mastiff',
'Australian Shepherd'
];

findOne(id: number) {
if (id >= 0 && id < this.dogBreeds.length) {
return { breed: this.dogBreeds[id] };
}
// NestJS has built-in HTTP exception classes for common errors.
throw new NotFoundException('Dog breed not found');
}
}

// The controller

import { Controller, Get, Param, ParseIntPipe } from '@nestjs/common';
import { DogsService } from './dogs.service';

@Controller('dogs')
export class DogsController {
// NestJS injects the DogsService through the constructor.
// The 'private readonly' syntax is a TypeScript shorthand
// to both declare and initialize the dogsService member.
constructor(private readonly dogsService: DogsService) {}

@Get(':id')
findOneDog(@Param('id', ParseIntPipe) id: number) {
// We can now use the service's methods. The ParseIntPipe
// automatically converts the string URL parameter to a number.
return this.dogsService.findOne(id);
}
}

This style is typical of dependency injection frameworks like Angular, as well as Spring. It allows you to declare components as injectable, then consume them anywhere you need them.

In Nest, we’d just add these as modules to make them live.

Adonis.js

Like Nest, Adonis provides a controller layer that you wire together with routes. Adonis is inspired by the model-view-controller (MVC) pattern, so it also includes a layer for modelling data and accessing stores via an ORM. Finally, it provides a validator layer for ensuring data meets requirements.

Routes in Adonis are very simple:

Route.get('/dogs/:id', [DogsController, 'show'])

In this case, DogsController would be the handler for the route, and might look something like:

import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext' // Note, ioc means inversion of control, similar to dependency injection

export default class DogsController {
// The 'show' method handles the logic for the route
public async show({ params, response }: HttpContextContract) {
const id = Number(params.id);

// Check if the id is a valid number and within the array bounds
if (!isNaN(id) && id >= 0 && id < this.dogBreeds.length) {
// Use the response object to send a 200 OK JSON response
return response.ok({ breed: this.dogBreeds[id] });
} else {
// Send a 404 Not Found response
return response.notFound({ error: 'Dog breed not found' });
}
}
}

Of course, in a real application, we could define a model layer to handle the actual data access.

Sails

Sails is another MVC-style framework. It is one of the original one-stop-shopping frameworks for Node and includes an ORM layer (Waterline), API generation (Blueprints), and realtime support, including WebSockets.

Sails strives for conventional operation. For example, here’s how you might define a simple model for dogs:

/**
* Dog.js
*
* @description:: A model definition represents a database table/collection.
* @docs:: https://sailsjs.com/docs/concepts/models
*/
module.exports = {
attributes: {
breed: { type: 'string', required: true },
},
};

If you run this in Sails, the framework will generate default routes and wire up a NoSQL or SQL datastore based on your configuration. Sails also provides the option to override these defaults and add in your own custom logic.

Full-stack frameworks

Also known as meta-frameworks, these tools combine a front-end framework with a solid back end and various CLI niceties like build chains.

Next.js

Next is a React-based framework built by Vercel. It is largely responsible for the huge growth in popularity of these types of frameworks. Next was the first framework to bring together back-end API definitions with the front end that consumes them. It also introduced file-system routing. In Next and other full-stack frameworks, you get both parts of your stack in one place and you can run them together during development.

In Next, we could define a route at pages/api/dogs/[id].js like so:

export default function handler(req, res) {
// `req.query.id` comes from the dynamic filename [id].js
const { id } = req.query;
const parsedId = parseInt(id, 10);

if (parsedId >= 0 && parsedId < dogBreeds.length) {
// If the ID is valid, return the data
res.status(200).json({ breed: dogBreeds[parsedId] });
} else {
// Otherwise, return a 404 error
res.status(404).json({ error: 'Dog breed not found' });
}
}

We’d then define the UI component to interact with this route at pages/dogs/[id].js:

import React from 'react';

// This is the React component that renders the page.
// It receives the `dog` object as a prop from getServerSideProps.
function DogPage({ dog }) {
// Handle the case where the dog wasn't found
if (!dog) {
return Dog Breed Not Found;
}

return (

Dog Breed Profile
Breed Name: {dog.breed}

);
}

// This function runs on the server before the page is sent to the browser.
export async function getServerSideProps(context) {
const { id } = context.params; // Get the ID from the URL

// Fetch data from our own API route on the server.
const res = await fetch(`

// If the fetch was successful, parse the JSON.
const dog = res.ok? await res.json(): null;

// Pass the fetched data to the DogPage component as props.
return {
props: {
dog,
},
};
}

export default DogPage;

Nuxt.js

Nuxt is the same idea as Next, but applied to the Vue front end. The basic pattern is the same, though. First, we’d define a back-end route:

// server/api/dogs/[id].js

// defineEventHandler is Nuxt's helper for creating API handlers.
export default defineEventHandler((event) => {
// Nuxt automatically parses route parameters.
const id = getRouterParam(event, 'id');
const parsedId = parseInt(id, 10);

if (parsedId >= 0 && parsedId < dogBreeds.length) {
return { breed: dogBreeds[parsedId] };
} else {
// Helper to set the status code and return an error.
setResponseStatus(event, 404);
return { error: 'Dog breed not found' };
}
});

Then, we’d create the UI file in Vue:

// pages/dogs/[id].vue



Loading...


{{ error.data.error }}


Dog Breed Profile
Breed Name: {{ dog.breed }}



SvelteKit

SvelteKit is the full-stack framework for the Svelte front end. It’s similar to Next and Nuxt, with the main difference being the front-end technology.

In SvelteKit, a back-end route looks like so:

// src/routes/api/dogs/[id]/+server.js

import { json, error } from '@sveltejs/kit';

// This is our data source for the example.
const dogBreeds = [
'Shih Tzu',
'Australian Cattle Dog',
'Great Pyrenees',
'Tibetan Mastiff',
];

/** @type {import('./$types').RequestHandler} */
export function GET({ params }) {
// The 'id' comes from the [id] directory name.
const id = parseInt(params.id, 10);

if (id >= 0 && id < dogBreeds.length) {
// The json() helper creates a valid JSON response.
return json({ breed: dogBreeds[id] });
}

// The error() helper is the idiomatic way to return HTTP errors.
throw error(404, 'Dog breed not found');
}

SvelteKit usually splits the UI into two components. The first component is for loading the data (which can then be run on the server):

// src/routes/dogs/[id]/+page.js

import { error } from '@sveltejs/kit';

/** @type {import('./$types').PageLoad} */
export async function load({ params, fetch }) {
// Use the SvelteKit-provided `fetch` to call our API endpoint.
const response = await fetch(`/api/dogs/${params.id}`);

if (response.ok) {
const dog = await response.json();
// The object returned here is passed as the 'data' prop to the page.
return {
dog: dog
};
}

// If the API returns an error, forward it to the user.
throw error(response.status, 'Dog breed not found');
}

The second component is the UI:

// src/routes/dogs/[id]/+page.svelte

Dog Breed Profile
Breed Name: {data.dog.breed}

Conclusion

This article was a survey of the most popular Node web frameworks, with a look at how various frameworks handle route parameters and request handling in a simple file server application. The Node ecosystem is also not limited to web frameworks. It has good data layer/ORM tools like Prisma, Drizzle, and Sequelize; CLI libraries like Oclif, Commander.js, and Inquirer.js; and native-application frameworks like Tauri and Electron.
https://www.infoworld.com/article/2253639/the-complete-guide-to-nodejs-frameworks.html

Related News

News copyright owned by their original publishers | Copyright © 2004 - 2025 Zicos / 440Network
Current Date
Dec, Wed 3 - 11:16 CET