Skip to content

Migrating from Express

BananaJS runs on Express — middleware, Request/Response, and ecosystem packages still work. You can migrate one route at a time: mount BananaJS routes under a prefix and leave the rest of your Express app untouched, or run the codemod and convert handlers in bulk.

Incremental mount with BananaRouter

Mount generated BananaJS routes under a prefix while legacy Express routes stay in place:

typescript
import express from 'express'
import { BananaRouter } from '@banana-universe/bananajs'
import { UserController } from './user.controller.js'

const app = express()
app.use(express.json())

const bananaRoutes = BananaRouter([UserController])
app.use('/api/v2', bananaRoutes)

app.get('/legacy', (_req, res) => res.send('ok'))

app.listen(3000)

BananaRouter is a function that returns an Express Router — use it directly with app.use.

A migrated endpoint — before & after

Before (Express, four concerns tangled):

typescript
router.post('/users', async (req, res) => {
  if (!req.body?.email || !req.body?.name) {
    return res.status(400).json({ error: 'Missing fields' })
  }
  try {
    const user = await db.users.create(req.body)
    res.json({ success: true, data: user })
  } catch (e) {
    res.status(500).json({ error: e.message })
  }
})

After (BananaJS, handler is only business logic):

typescript
@Controller('users')
export class UserController extends BaseController {
  @Post('')
  @Body(CreateUserSchema)
  async create(req: Request, res: Response) {
    const user = await this.service.create(req.body)
    return this.ok(res, 'User created', user)
  }
}

Validation, error shape, and response envelope are all handled by the framework. The codemod (bjs migrate) emits this *.controller.ts shape from your existing Express routes.

What changes conceptually

Express habitBananaJS pattern
Manual router.get/post@Controller + @Get/@Post on class methods
Manual body/query validation@Body(schema) / @Query(schema) with validation
Ad-hoc res.status().json()BaseController.ok or SuccessResponse + send
Central if (err) in every handlerThrow ApiError subclasses + ErrorMiddleware

Codemod

The CLI includes bananajs migrate — Express route codemod that emits *.controller.ts starters (see CLI).

Tests

Use BananaTestApp from @banana-universe/bananajs/testing to hit your app over HTTP without binding a real port in every test.