Zum Hauptinhalt springen
Echtzeit-Radar & Feeds
Alle RSS Feeds ➔
👥 Community & Social
Apple iOS & macOSGlenn Fleishman Condemns Stalker-Enabling AirTag Silencer(01.10.2026 um 17:43 Uhr)
•
Apple iOS & macOSThe Brakes Are Not Connected(01.10.2026 um 22:57 Uhr)
•
Apple iOS & macOSHow To Screenshot An Entire Web Page On Your Mac(01.10.2026 um 17:00 Uhr)
••
Podcasts & Audio BriefingsApple’s first touchscreen OLED MacBook Pro could arrive as soon as October(01.10.2026 um 18:15 Uhr)
••
Apple iOS & macOSApple TV expands library with new batch of free classic movies(01.10.2026 um 20:45 Uhr)
••••
Apple iOS & macOSGlenn Fleishman Condemns Stalker-Enabling AirTag Silencer(01.10.2026 um 17:43 Uhr)
•
Apple iOS & macOSThe Brakes Are Not Connected(01.10.2026 um 22:57 Uhr)
•
Apple iOS & macOSHow To Screenshot An Entire Web Page On Your Mac(01.10.2026 um 17:00 Uhr)
••
Podcasts & Audio BriefingsApple’s first touchscreen OLED MacBook Pro could arrive as soon as October(01.10.2026 um 18:15 Uhr)
••
Apple iOS & macOSApple TV expands library with new batch of free classic movies(01.10.2026 um 20:45 Uhr)
••••
Intelligence View
⚡ tsecurity.de Intelligence

From Express.js to Fastify [part 1]

I've been using Express.js for years on different projects. Express.js is mature, stable, reliable and has a lot of community support. Undoubtedly, Express.js…

Beitrag
0
Seite
0
↗ Quelle (dev.to)
Social ReaktionenReagiere als Erste:r — dein Feedback zählt!

I've been using Express.js for years on different projects. Express.js is mature, stable, reliable and has a lot of community support. Undoubtedly, Express.js is one of the most important and popular NodeJS frameworks. For me, it's just a little bit old (this project started in 2012 August) and I always prefer to try younger but has a great potential library on my projects.



I have known Fastify for a while. I recently tried Fastify for the first time, and it took me a few minutes to decide to switch. Fastify has quite a few plugins (it's like middleware in the Express world) and everything I need (for now) on the official plugin list.



Another reason I'd switch to Fastify is its outstanding performance when compared to Express. For example, in this article, the author explained how she ran benchmarks against Fastify and Express.js:




Express successfully served about 20,000 requests per second, with a read average of 4.8 Mega, and served 221,000 total requests. Fastify successfully served about 48,000 requests per second on average and the total requests served was 526,000.




If you're still using Express, you might wonder if it's worth switching to Fastify like I was. I spent 2 days on my projects to switch to Fasity without big hassle and I'd like to share my experience about how I migrated my project, so I created this series to explore the benefits of switching from Express.js to Fastify, as well as the potential challenges you might encounter. It'll also provide a hands-on guide for migrating your existing Express applications to Fastify.






The change I made is in this PR. This PR looks big because of the file path change (it's a false alarm). I've separated concerns into different files, so most files I changed were related to HTTP request handler. I barely touched any files related to the DB connection or S3 bucket.



This is my rough project's structure (from server folder) and I only touched the files which marked the star symbol:




server
├── package.json
├── README.md
├── tsconfig.json
└── src
├── app.ts*
├── controllers*
│   ├── album-controller.ts
│   ├── auth-controller.ts
│   └── base-controller.ts
├── routes*
│   ├── album-route.ts
│   ├── auth-middleware.ts
│   └── auth-route.ts
├── schemas
│   ├── albums.ts
│   └── album-tag.ts
├── services
│   ├── album-service.ts
│   ├── config.ts
│   └── dynamodb-service.ts
└── utils*
   └── json-response.ts









Plugins



Let's start from the application entry point: app.ts. First of all, install the necessary Fastify libraries:




$npm install -S fastify fastify-plugin @fastify/auth @fastify/cookie @fastify/cors @fastify/helmet @fastify/multipart @fastify/rate-limit @fastify/throttle






In Express.js, we inject middleware functions into request/response processing pipeline to modify an application's behaviour. In Fastify, we register what we call "middleware" in Express.js as a plugin. This plugin can be an official one or we can build our plugin. My project is fairly small, so the most important plugins are auth, cookies, cors, helmet, multipart, rate-limit and throttle.




// app.ts
/********* ExpressJs *********/
...
// Create Express.js instance
const app: Application = express();

app.use(cors());
app.use(cookieParser());
app.use(bodyParser.json({ limit: '500kb' }));
app.use(bodyParser.urlencoded({ extended: false, limit: '500kb' }));
app.use(helmet());
...

/********* Fastify *********/
...
// Create Fastify instance
const app: FastifyInstance = Fastify();

await app.register(cors);
await app.register(helmet);
await app.register(rateLimit);
await app.register(auth);
await app.register(cookie);
await app.register(multipart);
await app.register(throttle);
...






(Wait, did I mention we can use async/await in Fastify like other modern apps? c'mon, it's 2024, I'd be happier if I could write modern code😜 )






CORS



Let's dive into CORS a little bit more. In Express.js, I had this cors configuration:




// app.ts
const corsOptions = {
allowedHeaders: ['Origin, Content-Type, Accept, Authorization, X-Requested-With'],
credentials: true,
methods: ['GET, POST, PUT, DELETE, OPTIONS'],
optionsSuccessStatus: 200,
origin: (origin, callback) => {
const allowedOrigins = ['http://localhost:9000'];
if (origin === undefined || allowedOrigins.indexOf(origin) > -1) {
callback(null, true);
return;
}
// If this origin is not allowed, return an error in the callback function
callback(new Error('This origin is not allowed'), false);
},
preflightContinue: true,
}






To use this configuration, we need to inject middleware into the request handler:




// app.ts
app.use(cors());
app.get('/products/:id', cors(corsOptions), (req, res, next) => {
res.json({msg: 'This is CORS-enabled for an allowed domain.'})
})






In Fasity, all we need to do is add this configuration into CORS plugin and register it:




// app.ts
await app.register(cors, corsOptions);









🍪-parser and body-parser



In Expres.js, we need to inject cookie-parser and body-parser middleware as below:




// app.ts
app.use(cookieParser());
app.use(bodyParser.json({ limit: '500kb' }));
app.use(bodyParser.urlencoded({ extended: false, limit: '500kb' }));






In Fastify, it has a native content body parser, so we don't need body-parser anymore. If we want to change the body limit (default is 1MB), we can add this config when we create Fastify instance:




// app.ts
const app: FastifyInstance = Fastify({bodyLimit: 512 * 1024}); // 512kb

await app.register(cookie);









Rate limit, throttle and 🪖



Rate limit, throttle and helmet are other must-have plugins:




// app.ts
await app.register(helmet);
await app.register(rateLimit, { max: 100 });
await app.register(throttle, {
bytesPerSecond: 1024 * 128, // 128KB/s
});









Routes



In Express.js, I implemented routes based on MVC design pattern (though there is no "view"). In the below code example, I omitted some codes and will talk about them later.



Let's have a look at some basic files first:




//model.ts, Base controller interface
import { RequestHandler } from 'express';

export interface BaseController {
findAll?: RequestHandler;

findOne?: RequestHandler;

create?: RequestHandler;

update?: RequestHandler;

delete?: RequestHandler;
}






This controller will be extended by other "real" controllers:




// base-controller.ts
import { RequestHandler, Response } from 'express';
import { BaseController as IBaseController } from '../models';
import JsonResponse from '../utils/json-response';

export abstract class BaseController implements IBaseController {
abstract findAll: RequestHandler;

abstract findOne: RequestHandler;

abstract create: RequestHandler;

abstract update: RequestHandler;

abstract delete: RequestHandler;

public ok<T>(res: Response, message = '', data?: T) {
if (data) {
return new JsonResponse<T>(200).success(res, message, data);
} else {
return new JsonResponse(200).success(res, message);
}
}

public fail(res: Response, message: string) {
return new JsonResponse(500).error(res, message);
}

public unauthorized(res: Response, message = '') {
return new JsonResponse(401).unauthorized(res, message);
}

public clientError(res: Response, message = '') {
return new JsonResponse(400).error(res, message);
}
}









// json-response.ts
import { Response } from 'express';
...

export default class JsonResponse<T> {
private readonly code: number;
private _status: string;

constructor(statusCode = 200) {
this.code = statusCode;
this._status = '';
}

unauthorized = (res: Response, message: string) => {
this._status = STATUS_UNAUTHORIZED;
return res.status(this.code).json({
code: this.code,
status: this._status,
message,
} as ResponseStatus);
};

error = (res: Response, message: string) => {
this._status = STATUS_ERROR;
return res.status(this.code).json({
code: this.code,
status: this._status,
message,
} as ResponseStatus);
};

success = (res: Response, message: string, data?: T) => {
this._status = STATUS_SUCCESS;
if (data) {
return res.status(this.code).json({
code: this.code,
status: this._status,
message,
data,
} as ApiResponse<T>);
} else {
return res.status(this.code).json({
code: this.code,
status: this._status,
message,
} as ResponseStatus);
}
};

setStatus(status: string) {
this._status = status;
return this;
}
}







Let's dive into one of the controllers I created:




// album-controller.ts
import { Request, RequestHandler, Response } from 'express';
import { Album } from '../schemas/album';
import AlbumService from '../services/album-service';
import { BaseController } from './base-controller';

const albumService = new AlbumService();

export default class AlbumController extends BaseController {
findAll: RequestHandler = (req: Request, res: Response) => {
try {
const query = ({ isPrivate }: any, { eq }: any) => `${eq(isPrivate, false)}`;

const albumList = await albumService.findAll(query);

return this.ok<Album[]>(res, 'ok', albumList);
} catch (err: any) {
return this.fail(res, 'Failed to query photo album');
}
};

create: RequestHandler = (req: Request, res: Response) => {
const album = req.body as Album;

try {
const result = await albumService.create(album);

if (result) {
return this.ok(res, 'Album created');
}
return this.fail(res, 'Failed to create photo album');
} catch (err: any) {
return this.fail(res, 'Failed to create photo album');
}
};
...
}






We now can use the above controller in the route:




//album-route.ts
import express from 'express';
import AlbumController from '../controllers/album-controller';
import { verifyJwtClaim, verifyUserPermission } from './auth-middleware';

export const router = express.Router();
const controller = new AlbumController();

router.get('', controller.findAll);
router.post('', verifyJwtClaim, verifyUserPermission, controller.create);
...






Finally, use this route in the app:




//app.ts
import { router as albumRoute } from './routes/album-route';

app.use('/api/albums', albumRoute);






How do we create routes in Fastify? Everything that works outside app.ts needs to be registered as a plugin.



Firstly, we need to update the base controller interface:




// model.ts
import { FastifyRequest, RouteHandler } from 'fastify';

export interface BaseController {
findAll?: RouteHandler;

findOne?: RouteHandler;

create?: RouteHandler;

update?: RouteHandler;

delete?: RouteHandler;
}






We need to switch to Fastify RouteHandler from Express.js RequestHandler:




// base-controller.ts
import { FastifyReply, RouteHandler } from 'fastify';
import { BaseController as IBaseController } from '../models.js';
import JsonResponse from '../utils/json-response.js';

export abstract class BaseController implements IBaseController {
abstract findAll: RouteHandler;

abstract findOne: RouteHandler;

abstract create: RouteHandler;

abstract update: RouteHandler;

abstract delete: RouteHandler;

public ok<T>(reply: FastifyReply, message = '', data?: T) {
if (data) {
return new JsonResponse<T>(200).success(reply, message, data);
} else {
return new JsonResponse(200).success(reply, message);
}
}

public clientError(reply: FastifyReply, message = '') {
return new JsonResponse(400).error(reply, message);
}

public unauthorized(reply: FastifyReply, message = '') {
return new JsonResponse(401).unauthorized(reply, message);
}

public fail(reply: FastifyReply, message: string) {
return new JsonResponse(500).error(reply, message);
}
}






Update every Reponse to FastifyReply:




// json-response.ts
import { FastifyReply } from 'fastify';
...

export default class JsonResponse<T> {
private readonly code: number;
private _status: string;

constructor(statusCode = 200) {
this.code = statusCode;
this._status = '';
}

unauthorized(reply: FastifyReply, message: string) {
this._status = STATUS_UNAUTHORIZED;
return reply.code(this.code).send({
code: this.code,
status: this._status,
message,
} satisfies ResponseStatus);
}

error(reply: FastifyReply, message: string) {
this._status = STATUS_ERROR;
return reply.code(this.code).send({
code: this.code,
status: this._status,
message,
} satisfies ResponseStatus);
}

success(reply: FastifyReply, message: string, data?: T) {
this._status = STATUS_SUCCESS;
if (data) {
return reply.code(this.code).send({
code: this.code,
status: this._status,
message,
data,
} satisfies ResponseStatus);
} else {
return reply.code(this.code).send({
code: this.code,
status: this._status,
message,
} satisfies ResponseStatus);
}
}

setStatus(status: string) {
this._status = status;
return this;
}
}






Update Express.js request/response/request handler to FastifyReply, FastifyRequest, RouteHandler:




// album-controller.ts
import { FastifyReply, FastifyRequest, RouteHandler } from 'fastify';
import { Album } from '../schemas/album.js';
import AlbumService from '../services/album-service.js';
import { BaseController } from './base-controller.js';

const albumService = new AlbumService();

export default class AlbumController extends BaseController {
findAll: RouteHandler = async (request: FastifyRequest, reply: FastifyReply) => {
try {
const query = ({ isPrivate }: any, { eq }: any) => `${eq(isPrivate, false)}`;
}

const albumList = await albumService.findAll(query);

return this.ok<Album[]>(reply, 'ok', albumList);
} catch (err: any) {
return this.fail(reply, 'Failed to query photo album');
}
};

create: RouteHandler = async (request: FastifyRequest, reply: FastifyReply) => {
const album = request.body as Album;
album.createdBy = (request as RequestWithUser).user?.email ?? 'unknown';
album.updatedBy = (request as RequestWithUser).user?.email ?? 'unknown';

try {
const result = await albumService.create(album);

if (result) {
return this.ok(reply, 'Album created');
}
return this.fail(reply, 'Failed to create photo album');
} catch (err: any) {
return this.fail(reply, 'Failed to create photo album');
}
};
}









Create Fastify plugin



User controller in Fastify route plugin (we will talk about auth middleware later). Create a function that takes three parameters, the Fastify instance, an options object, and the done callback. At the end of the function, we must call done() callback:




// album-route.ts
import { FastifyInstance, FastifyPluginCallback } from 'fastify';
import fastifyPlugin from 'fastify-plugin';
import AlbumController from '../controllers/album-controller.js';
import { verifyJwtClaim, verifyUserPermission } from './auth-middleware.js';

const controller = new AlbumController();

const albumRoute: FastifyPluginCallback = (instance: FastifyInstance, _opt, done) => {
instance.get('/api/albums', controller.findAll);

instance.post('/api/albums', {
onRequest: instance.auth([verifyJwtClaim, verifyUserPermission], {
relation: 'and',
}),
handler: controller.create,
});
...

// IMPORTANT!
done();
};

// Wrap function in the Fastify plugin
export default fastifyPlugin(albumRoute);






Register this plugin in the app like other official plugins:




// app.ts
...
app.register(albumRoute);

try {
await app.listen({ port: 3000 });
} catch (err) {
app.log.error(err);
}









Next part: Authentication, Hooks, Middleware, Decorators, and Validation



I will dive deeper into some advanced concepts of Fastify, such as authentication, hooks, middleware, decorators, and validation by using my project as an example.



Until next time, thanks for reading!

🔍 CTI & Forensik

Cyber Threat Intelligence & Forensik

ATT&CK-Navigator · IoC-Radar · Exploit-Belege
CTI Threat Relationship Graph
Akteure · Techniken · Beziehungen
2 Knoten · 1 Relationen
CVE / Incident Threat Actor Software MITRE ATT&CK CWE Weakness IoC
Ähnliche Beiträge
🔍 Verwandte News

Auch interessante Nachrichten From Express.js to Fastify [part 1]

Thematisch verwandte Begriffe: From, Expressjs, Fastify, part · 6 Treffer

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

💬 Kommentare werden geladen…
Zum Aktualisieren ziehen
tsecurity.de Icon
Offline-Lesen, Eilmeldungen & 0ms Ladezeit

Installiere tsecurity.de direkt auf deinen Home-Bildschirm für das ultimative Vollbild-Magazinerlebnis ohne Browser-Leisten.

Nächster Beitrag