Skip to content

Express Adapter

The Veloce-TS Express adapter lets you mount a Veloce-TS application inside an existing Express.js app — or use Express as the HTTP transport layer underneath Veloce-TS. This is useful when migrating from Express to Veloce-TS incrementally, or when you need Express-specific middleware (e.g. Passport.js, multer, session).

:::note v0.4.0 Rewrite The Express adapter was completely rewritten in v0.4.0 for full ESM compatibility, correct body handling, and proper error delegation. If you were using an older version, review the migration notes below. :::


Express is a peer dependency — install it alongside Veloce-TS:

Terminal window
# Using Bun
bun add veloce-ts express
bun add -d @types/express
# Using npm
npm install veloce-ts express
npm install --save-dev @types/express

import express from 'express';
import { VeloceTS, Controller, Get, Post, Body } from 'veloce-ts';
import { ExpressAdapter } from 'veloce-ts/adapters/express';
import { z } from 'zod';
// 1. Define your Veloce-TS app as usual
const UserSchema = z.object({
name: z.string().min(2),
email: z.string().email(),
});
@Controller('/users')
class UserController {
@Get('/')
async list() {
return [{ id: 1, name: 'John' }];
}
@Post('/')
async create(@Body(UserSchema) user: z.infer<typeof UserSchema>) {
return { id: 2, ...user };
}
}
const veloce = new VeloceTS({ title: 'My API', docs: true });
veloce.include(UserController);
await veloce.compile();
// 2. Create an Express app and mount Veloce-TS
const app = express();
// Parse JSON bodies for Express routes
app.use(express.json());
// Mount the Veloce-TS adapter — it handles all requests under /api
const adapter = new ExpressAdapter(veloce);
app.use('/api', adapter.getRouter());
// You can still have regular Express routes alongside
app.get('/health', (req, res) => {
res.json({ status: 'ok', framework: 'express+veloce' });
});
app.listen(3000, () => {
console.log('Server running on http://localhost:3000');
console.log('Veloce-TS routes available at http://localhost:3000/api');
console.log('Swagger UI at http://localhost:3000/api/docs');
});

Adding Express Middleware Before the Bridge

Section titled “Adding Express Middleware Before the Bridge”

The v0.4.0 adapter accepts a pre-created Express app as the second argument to the constructor. This lets you add middleware (auth, rate limiting, multipart, etc.) that runs before requests reach Veloce-TS:

import express from 'express';
import passport from 'passport';
import multer from 'multer';
import { ExpressAdapter } from 'veloce-ts/adapters/express';
const expressApp = express();
// Add any Express middleware you need
expressApp.use(express.json());
expressApp.use(express.urlencoded({ extended: true }));
expressApp.use(passport.initialize());
const upload = multer({ dest: 'uploads/' });
// Create the adapter, passing the pre-configured Express app
const adapter = new ExpressAdapter(veloce, expressApp);
// Now mount to your main app or listen directly
expressApp.use('/api', adapter.getRouter());
expressApp.listen(3000);

Mount Veloce-TS on any sub-path of your Express app:

const mainApp = express();
mainApp.use(express.json());
// v1 API powered by Veloce-TS
const v1Adapter = new ExpressAdapter(veloceV1App);
mainApp.use('/api/v1', v1Adapter.getRouter());
// v2 API powered by another Veloce-TS app
const v2Adapter = new ExpressAdapter(veloceV2App);
mainApp.use('/api/v2', v2Adapter.getRouter());
// Legacy routes still on Express
mainApp.use('/legacy', legacyRouter);
mainApp.listen(3000);

The v0.4.0 adapter correctly delegates unexpected errors to the Express error pipeline via next(err), so your custom Express error handlers are called:

import express, { Request, Response, NextFunction } from 'express';
import { ExpressAdapter } from 'veloce-ts/adapters/express';
const app = express();
app.use(express.json());
const adapter = new ExpressAdapter(veloce);
app.use('/api', adapter.getRouter());
// This error handler will be called for any unhandled errors,
// including those thrown by Veloce-TS handlers
app.use((err: Error, req: Request, res: Response, next: NextFunction) => {
console.error('Unhandled error:', err);
res.status(500).json({
error: 'Internal Server Error',
message: process.env.NODE_ENV === 'development' ? err.message : undefined,
});
});
app.listen(3000);

The v0.4.0 adapter correctly handles both raw (Buffer) and pre-parsed bodies:

  • JSON/urlencoded: If Express middleware has already parsed the body (e.g. via express.json()), the adapter uses the parsed value
  • Raw buffer: If the body is raw (e.g. file uploads, webhooks), it’s passed through correctly
  • transfer-encoding header: The adapter omits this header when forwarding responses, which was a source of bugs in older versions
// ✓ Works correctly with body parsers active
app.use(express.json());
app.use('/api', adapter.getRouter());
// ✓ Also works without body parsers (raw body)
app.use('/webhooks', adapter.getRouter());

If you were using the Express adapter from v0.3.x or earlier, here are the changes:

// Before (v0.3.x)
import { ExpressAdapter } from 'veloce-ts';
// After (v0.4.0)
import { ExpressAdapter } from 'veloce-ts/adapters/express';
// Before
const adapter = new ExpressAdapter(veloceApp);
// After — same, but now accepts optional pre-created Express instance
const adapter = new ExpressAdapter(veloceApp);
// or
const adapter = new ExpressAdapter(veloceApp, expressApp);

The adapter no longer needs declare const require: any workarounds. It uses Function('return require')() internally for lazy Express loading, which works seamlessly in ESM projects.


Use the Express adapter when:

ScenarioRecommendation
Starting a new projectUse Veloce-TS standalone (Hono adapter) for best performance
Migrating an Express app to Veloce-TSUse the adapter to migrate route by route
Need Passport.js, multer, or other Express-only middlewareUse the adapter
Deploying on a platform that only supports ExpressUse the adapter
Need maximum performanceUse Veloce-TS standalone