Features
Docs
CLI
Benchmarks
Examples

© 2024 MoroJs

Migration Guide

Step-by-step guides for migrating from Express, Fastify, Hono, NestJS, and other frameworks to MoroJS.

Why Migrate to MoroJS?

Performance

68k+ req/sec - up to 10x faster than 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

Core Concepts

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

Express Code

Express App

typescript

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});

MoroJS Equivalent

MoroJS App

typescript

1import { createApp, z } from '@morojs/moro';
2
3const app = 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(({ params }) => {
18    // params.id is type-safe
19    return { 
20      userId: 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(({ body }) => {
32    // body is validated & typed
33    return {
34      message: 'User created',
35      user: body
36    };
37  });
38
39await app.listen(() => {
40  console.log('Server running on port 3000');
41});

Migration Steps

  1. Replace express() with createApp()
  2. Remove middleware imports (body-parser, cors) - they're built-in
  3. Convert routes to chainable API with validation
  4. Replace manual validation with Zod schemas
  5. Update app.listen() to await app.listen()

Migrating from Fastify

Key Differences

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

Fastify Code

Fastify App

typescript

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});

MoroJS Equivalent

MoroJS App

typescript

1import { createApp, z } from '@morojs/moro';
2
3const app = 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 ({ body }) => {
26    // body is automatically typed
27    const { name, email, age } = 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

  1. Replace Fastify() with createApp()
  2. Convert JSON Schema to Zod schemas
  3. Remove plugin registrations - use built-in features
  4. Update route definitions to chainable API
  5. Replace request/reply with req/res

Migrating from Hono

Similarities & Differences

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

Hono Code

Hono App

typescript

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;

MoroJS Equivalent

MoroJS App

typescript

1import { createApp, z } from '@morojs/moro';
2
3const app = 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(({ body }) => {
16    // body is validated & typed
17    return {
18      message: 'User created',
19      user: body
20    };
21  });
22
23// GET route with params
24app.get('/users/:id')
25  .params(z.object({
26    id: z.string()
27  }))
28  .handler(({ params }) => {
29    return {
30      userId: params.id,
31      message: 'User retrieved'
32    };
33  });
34
35export default app;

Migration Steps

  1. Replace new Hono() with createApp()
  2. Remove @hono/zod-validator - use built-in validation
  3. Convert context API (c) to destructured parameters
  4. Replace c.json() with direct returns
  5. Update c.req.param() to typed params object

Migrating from NestJS

Architectural Shift

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

NestJS Code

NestJS Controller

typescript

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}

MoroJS Equivalent

MoroJS Module

typescript

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 = createApp();
24
25// Register service in DI container
26app.registerService('userService', UserService);
27
28// Routes
29app.post('/users')
30  .body(CreateUserSchema)
31  .handler(async ({ body, container }) => {
32    const userService = container.resolve('userService');
33    return userService.create(body);
34  });
35
36app.get('/users/:id')
37  .handler(async ({ params, container }) => {
38    const userService = container.resolve('userService');
39    return userService.findOne(params.id);
40  });

Migration Steps

  1. Convert controllers to functional route handlers
  2. Replace DTOs with Zod schemas
  3. Move services to MoroJS DI container or use directly
  4. Replace decorators with chainable API
  5. Update module imports to MoroJS module system
  6. Convert guards/interceptors to middleware

Common Migration Patterns

Middleware Pattern

Any Framework → MoroJS

typescript

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

Error Handlers

typescript

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

JWT Auth Migration

typescript

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(({ user }) => {
27    return { user }; // user is typed
28  });

Database Integration

ORM Migration

typescript

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 ({ container }) => {
15    const db = 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

Next Steps