Migrating from Fastify
Fastify users choose it for speed and built-in schema validation. StreetJS keeps
those properties — a native HTTP server on node:http, built-in input validation,
and bounded-memory streaming — while moving routing to a decorator model and
replacing the plugin-encapsulation system with a DI container plus middleware.
Mental-model mapping
| Fastify | StreetJS |
|---|---|
const app = Fastify() |
const app = streetApp({ port }) |
app.get('/x', opts, handler) |
@Get('/x') on a @Controller |
request / reply |
a single ctx: StreetContext |
reply.send(obj) |
ctx.json(obj) |
reply.code(201).send(...) |
ctx.status(201).json(...) |
route schema: { body, params } |
@Validate({ body, params }) |
app.addHook('onRequest', ...) |
app.use(middleware) |
app.register(plugin) |
@Injectable() services + app.use(...) |
@fastify/cors |
corsMiddleware(...) |
@fastify/helmet |
securityHeaders(...) |
@fastify/jwt |
JwtService + authMiddleware |
Routing
Fastify
1
2
3
4
5
6
7
8
9
10
11
12
const app = require('fastify')();
app.get('/api/items/:id', async (request, reply) => {
return { id: request.params.id };
});
app.post('/api/items', async (request, reply) => {
reply.code(201);
return { created: request.body.name };
});
app.listen({ port: 3000 });
StreetJS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import 'reflect-metadata';
import { streetApp, Controller, Get, Post } from 'streetjs';
import type { StreetContext } from 'streetjs';
@Controller('/api/items')
class ItemController {
@Get('/:id')
async getOne(ctx: StreetContext) {
ctx.json({ id: ctx.params.id });
}
@Post('/')
async create(ctx: StreetContext) {
const body = ctx.body as { name: string };
ctx.status(201).json({ created: body.name });
}
}
const app = streetApp({ port: 3000 });
app.registerController(ItemController);
await app.listen();
Schema validation
Fastify validates via JSON Schema on the route. StreetJS uses the @Validate
decorator, which rejects invalid input with a safe 400 before the handler runs:
Fastify
1
2
3
4
5
6
7
8
9
app.post('/users', {
schema: {
body: {
type: 'object',
required: ['name'],
properties: { name: { type: 'string' }, age: { type: 'integer' } },
},
},
}, handler);
StreetJS
1
2
3
4
5
6
7
8
9
10
11
import { Controller, Post, Validate } from 'streetjs';
import type { StreetContext } from 'streetjs';
@Controller('/users')
class UserController {
@Post('/')
@Validate({ body: { name: { type: 'string', min: 1 }, age: { type: 'number' } } })
async create(ctx: StreetContext) {
ctx.status(201).json(ctx.body);
}
}
Hooks → middleware
Fastify lifecycle hooks (onRequest, preHandler) map to StreetJS middleware
registered with app.use(...):
1
2
3
4
5
6
7
8
9
10
import { streetApp, corsMiddleware, securityHeaders } from 'streetjs';
const app = streetApp({ port: 3000 });
app.use(corsMiddleware({ origin: true })); // ~ @fastify/cors
app.use(securityHeaders()); // ~ @fastify/helmet
app.use(async (ctx, next) => { // ~ onRequest hook
const start = Date.now();
await next();
ctx.res.setHeader('x-response-time', String(Date.now() - start));
});
Plugins → DI + middleware
Fastify’s register() encapsulation becomes plain @Injectable() services
resolved by the container, plus middleware for cross-cutting concerns. Shared
infrastructure (Redis, Stripe, S3, etc.) is available as official StreetJS plugins
through the signed plugin registry rather than Fastify plugins.
JWT auth
Fastify (@fastify/jwt) → StreetJS (JwtService + authMiddleware):
1
2
3
4
5
6
7
import { JwtService, authMiddleware, requireRoles } from 'streetjs';
const jwt = new JwtService({ secret: process.env.JWT_SECRET! });
const token = await jwt.sign({ sub: userId, roles: ['admin'] });
app.use(authMiddleware({ /* verification options */ }));
// guard routes with requireRoles('admin')
Checklist
- Replace
Fastify()withstreetApp({ port })andlisten({port})withlisten(). - Convert routes to
@Controllerclasses with@Get/@Post/.... - Move
request/replylogic ontoctx(ctx.params,ctx.body,ctx.json,ctx.status). - Convert route
schemato@Validate({...}). - Convert hooks to
app.use(...)middleware. - Replace
@fastify/*plugins with built-in middleware + official StreetJS plugins.
See also: Migrating from Express · Migrating from NestJS.