Getting Started
Migration Guide
Step-by-step guides for migrating from Express, Fastify, Hono, NestJS, and other frameworks to MoroJS.
Why Migrate to MoroJS?
Performance
740k+ req/sec on the native engine - up to 11x Express with native TypeScript support
Type Safety
End-to-end type inference with built-in Zod validation
Zero Config
Production-ready with intelligent routing and automatic middleware ordering
Migrating from Express
Express
- Middleware-first approach
- Manual type definitions
- Callback-based handlers
- Manual validation setup
MoroJS
- Intelligent middleware ordering
- Automatic type inference
- Modern async/await
- Built-in Zod validation
1import express from 'express';
2import bodyParser from 'body-parser';
3import cors from 'cors';
4
5const app = express();
6
7// Manual middleware setup
8app.use(bodyParser.json());
9app.use(cors());
10
11// Route with manual validation
12app.get('/users/:id', (req, res) => {
13 const id = req.params.id;
14 if (!id) {
15 return res.status(400).json({
16 error: 'Missing id'
17 });
18 }
19 res.json({
20 userId: id,
21 message: 'User retrieved'
22 });
23});
24
25// POST with manual validation
26app.post('/users', (req, res) => {
27 const { name, email } = req.body;
28
29 if (!name || !email) {
30 return res.status(400).json({
31 error: 'Missing fields'
32 });
33 }
34
35 if (!email.includes('@')) {
36 return res.status(400).json({
37 error: 'Invalid email'
38 });
39 }
40
41 res.json({
42 message: 'User created',
43 user: { name, email }
44 });
45});
46
47app.listen(3000, () => {
48 console.log('Server running on port 3000');
49});1import { createApp, z } from '@morojs/moro';
2
3const app = await createApp({
4 server: { port: 3000 },
5 // CORS & body parsing built-in
6 features: {
7 cors: true,
8 bodyParser: true
9 }
10});
11
12// Type-safe route with validation
13app.get('/users/:id')
14 .params(z.object({
15 id: z.string().uuid()
16 }))
17 .handler((req) => {
18 // req.params.id is type-safe
19 return {
20 userId: req.params.id,
21 message: 'User retrieved'
22 };
23 });
24
25// POST with automatic validation
26app.post('/users')
27 .body(z.object({
28 name: z.string().min(1),
29 email: z.string().email()
30 }))
31 .handler((req) => {
32 // req.body is validated & typed
33 return {
34 message: 'User created',
35 user: req.body
36 };
37 });
38
39await app.listen(() => {
40 console.log('Server running on port 3000');
41});Migration Steps
- Replace
express()withcreateApp() - Remove middleware imports (body-parser, cors) - they're built-in
- Convert routes to chainable API with validation
- Replace manual validation with Zod schemas
- Update
app.listen()toawait app.listen()
Migrating from Fastify
Fastify
- JSON Schema validation
- Plugin-based architecture
- Manual schema definitions
- TypeScript via @fastify/type-provider
MoroJS
- Zod/Joi/Yup validation
- Module-based architecture
- Type inference from schemas
- Native TypeScript support
1import Fastify from 'fastify';
2
3const fastify = Fastify({
4 logger: true
5});
6
7// Route with JSON Schema
8fastify.post('/users', {
9 schema: {
10 body: {
11 type: 'object',
12 required: ['name', 'email'],
13 properties: {
14 name: { type: 'string', minLength: 1 },
15 email: { type: 'string', format: 'email' },
16 age: { type: 'number', minimum: 18 }
17 }
18 },
19 response: {
20 200: {
21 type: 'object',
22 properties: {
23 message: { type: 'string' },
24 user: { type: 'object' }
25 }
26 }
27 }
28 }
29}, async (request, reply) => {
30 const { name, email, age } = request.body;
31
32 return {
33 message: 'User created',
34 user: { name, email, age }
35 };
36});
37
38// Plugin registration
39fastify.register(import('@fastify/cors'));
40
41await fastify.listen({
42 port: 3000,
43 host: '0.0.0.0'
44});1import { createApp, z } from '@morojs/moro';
2
3const app = await createApp({
4 server: {
5 port: 3000,
6 host: '0.0.0.0'
7 },
8 logger: {
9 level: 'info'
10 },
11 features: {
12 cors: true
13 }
14});
15
16// Route with Zod schema
17const CreateUserSchema = z.object({
18 name: z.string().min(1),
19 email: z.string().email(),
20 age: z.number().min(18).optional()
21});
22
23app.post('/users')
24 .body(CreateUserSchema)
25 .handler(async (req) => {
26 // req.body is automatically typed
27 const { name, email, age } = req.body;
28
29 return {
30 message: 'User created',
31 user: { name, email, age }
32 };
33 });
34
35await app.listen(() => {
36 console.log('Server running');
37});Migration Steps
- Replace
Fastify()withcreateApp() - Convert JSON Schema to Zod schemas
- Remove plugin registrations - use built-in features
- Update route definitions to chainable API
- Replace
request/replywithreq/res
Migrating from Hono
Both MoroJS and Hono prioritize modern TypeScript and edge runtime support. Here's what changes:
Hono
- Context-based API
- Manual validator integration
- Edge-first design
- Lightweight core
MoroJS
- Request/Response objects
- Built-in validation
- Multi-runtime adapters
- Full-featured framework
1import { Hono } from 'hono';
2import { z } from 'zod';
3import { zValidator } from '@hono/zod-validator';
4
5const app = new Hono();
6
7const UserSchema = z.object({
8 name: z.string().min(1),
9 email: z.string().email()
10});
11
12// Route with validator middleware
13app.post(
14 '/users',
15 zValidator('json', UserSchema),
16 (c) => {
17 const data = c.req.valid('json');
18
19 return c.json({
20 message: 'User created',
21 user: data
22 });
23 }
24);
25
26// GET route with params
27app.get('/users/:id', (c) => {
28 const id = c.req.param('id');
29
30 return c.json({
31 userId: id,
32 message: 'User retrieved'
33 });
34});
35
36export default app;1import { createApp, z } from '@morojs/moro';
2
3const app = await createApp({
4 server: { port: 3000 }
5});
6
7const UserSchema = z.object({
8 name: z.string().min(1),
9 email: z.string().email()
10});
11
12// Built-in validation
13app.post('/users')
14 .body(UserSchema)
15 .handler((req) => {
16 // req.body is validated & typed
17 return {
18 message: 'User created',
19 user: req.body
20 };
21 });
22
23// GET route with params
24app.get('/users/:id')
25 .params(z.object({
26 id: z.string()
27 }))
28 .handler((req) => {
29 return {
30 userId: req.params.id,
31 message: 'User retrieved'
32 };
33 });
34
35export default app;Migration Steps
- Replace
new Hono()withcreateApp() - Remove
@hono/zod-validator- use built-in validation - Convert context API (
c) to destructured parameters - Replace
c.json()with direct returns - Update
c.req.param()to typedparamsobject
Migrating from NestJS
NestJS uses decorators and dependency injection. MoroJS provides similar patterns with a more functional approach:
NestJS
- Decorator-based routing
- Class-based controllers
- Heavy DI container
- Module imports/exports
MoroJS
- Functional routing API
- Function-based handlers
- Lightweight DI container
- Module auto-discovery
1import {
2 Controller,
3 Get,
4 Post,
5 Body,
6 Param
7} from '@nestjs/common';
8import { CreateUserDto } from './dto/create-user.dto';
9import { UserService } from './user.service';
10
11@Controller('users')
12export class UserController {
13 constructor(
14 private readonly userService: UserService
15 ) {}
16
17 @Post()
18 async create(@Body() dto: CreateUserDto) {
19 return this.userService.create(dto);
20 }
21
22 @Get(':id')
23 async findOne(@Param('id') id: string) {
24 return this.userService.findOne(id);
25 }
26}
27
28// DTO with class-validator
29import { IsString, IsEmail, Min } from 'class-validator';
30
31export class CreateUserDto {
32 @IsString()
33 @MinLength(1)
34 name: string;
35
36 @IsEmail()
37 email: string;
38
39 @Min(18)
40 age?: number;
41}1import { createApp, z } from '@morojs/moro';
2
3// Validation schema
4const CreateUserSchema = z.object({
5 name: z.string().min(1),
6 email: z.string().email(),
7 age: z.number().min(18).optional()
8});
9
10// Service (can be injected via DI)
11class UserService {
12 async create(data: z.infer<typeof CreateUserSchema>) {
13 // Business logic
14 return { id: '1', ...data };
15 }
16
17 async findOne(id: string) {
18 // Database query
19 return { id, name: 'John' };
20 }
21}
22
23const app = await createApp();
24
25// Register service in DI container
26app.registerService('userService', UserService);
27
28// Routes
29app.post('/users')
30 .body(CreateUserSchema)
31 .handler(async (req) => {
32 const userService = req.container.resolve('userService');
33 return userService.create(req.body);
34 });
35
36app.get('/users/:id')
37 .handler(async (req) => {
38 const userService = req.container.resolve('userService');
39 return userService.findOne(req.params.id);
40 });Migration Steps
- 1Convert controllers to functional route handlers
- 2Replace DTOs with Zod schemas
- 3Move services to MoroJS DI container or use directly
- 4Replace decorators with chainable API
- 5Update module imports to MoroJS module system
- 6Convert guards/interceptors to middleware
Common Migration Patterns
Middleware Pattern
1// Old middleware pattern
2function authMiddleware(req, res, next) {
3 if (!req.headers.authorization) {
4 return res.status(401).send('Unauthorized');
5 }
6 next();
7}
8
9// MoroJS middleware
10const authMiddleware = (req, res, next) => {
11 if (!req.headers.authorization) {
12 return res.status(401).send('Unauthorized');
13 }
14 next();
15};
16
17// Use globally
18app.use(authMiddleware);
19
20// Or per-route
21app.get('/protected')
22 .middleware(authMiddleware)
23 .handler(() => ({ data: 'Protected' }));Error Handling
1// Old error handling
2app.use((err, req, res, next) => {
3 res.status(err.status || 500).json({
4 error: err.message
5 });
6});
7
8// MoroJS error handling
9app.onError((error, req, res) => {
10 const status = error.status || 500;
11 return res.status(status).json({
12 error: error.message,
13 stack: process.env.NODE_ENV === 'development'
14 ? error.stack
15 : undefined
16 });
17});Authentication
1// Any framework JWT
2import jwt from 'jsonwebtoken';
3
4function verifyToken(req, res, next) {
5 const token = req.headers.authorization?.split(' ')[1];
6 try {
7 const decoded = jwt.verify(token, SECRET);
8 req.user = decoded;
9 next();
10 } catch (error) {
11 res.status(401).send('Invalid token');
12 }
13}
14
15// MoroJS built-in auth
16import { createAuthMiddleware } from '@morojs/moro';
17
18const auth = createAuthMiddleware({
19 jwt: {
20 secret: process.env.JWT_SECRET
21 }
22});
23
24app.get('/profile')
25 .middleware(auth.requireAuth())
26 .handler((req) => {
27 return { user: req.user }; // req.user is typed
28 });Database Integration
1// Keep your existing ORM
2import { PrismaClient } from '@prisma/client';
3// or Drizzle, TypeORM, etc.
4
5const prisma = new PrismaClient();
6
7// Register in DI container
8app.registerService('db', () => prisma, {
9 lifecycle: 'singleton'
10});
11
12// Use in routes
13app.get('/users')
14 .handler(async (req) => {
15 const db = req.container.resolve('db');
16 const users = await db.user.findMany();
17 return { users };
18 });
19
20// Or use MoroJS database adapters
21import { createDatabaseAdapter } from '@morojs/moro';
22
23const db = createDatabaseAdapter({
24 type: 'postgresql',
25 url: process.env.DATABASE_URL
26});Migration Checklist
Step-by-Step Migration
1. Setup MoroJS Project
Install MoroJS and configure TypeScript for ESM
2. Migrate Routes Incrementally
Start with simple routes and gradually move complex ones
3. Convert Validation
Replace validation libraries with Zod schemas (or keep using adapters)
4. Update Middleware
Port middleware or use MoroJS built-in features
5. Migrate Services & Dependencies
Move business logic to MoroJS DI container or modules
6. Update Tests
Adapt tests to MoroJS testing utilities
7. Deploy & Monitor
Deploy to your target runtime and verify performance