Microservices
Morphis treats each server as an independent, tree-shaken unit. Running morphis build --server=api bundles only the code reachable from src/routes/api.ts and writes a single self-contained file to dist/api/index.js.
This means you can maintain multiple servers in one repo (e.g. api, ws, jobs) and build/deploy each one independently with zero overlap.
The default server
When you scaffold a new project, Morphis creates the api server automatically:
- .env.api
NAME=api
PORT=3000
MULTI_THREAD=trueThe package.json scripts are also wired up from the start:
{
"dev:api": "morphis dev --server=api",
"build:api": "morphis build --server=api",
"start:api": "morphis start --server=api"
}Adding a new server
Use morphis new:server to create any additional server:
morphis new:server wsMorphis will:
- Create
src/routes/ws.tswith a minimal health-check route. - Create
.env.wswith a uniquePORT(auto-incremented so it does not clash with existing servers). - Inject
dev:ws,build:ws, andstart:wsintopackage.json.
Server names may only contain letters, numbers, hyphens, and underscores.
The generated route file is a clean starting point:
import { Get, Router } from 'morphis';
const router = new Router();
router.get(() => ({ message: 'OK' }), [Get('/')]);
export default router;Example: a WebSocket server
Replace the scaffold with your own logic while keeping the same export default router contract:
import { Get, Router } from 'morphis';
const router = new Router();
router.get(() => ({ ok: true }), [Get('/health')]);
// Additional WebSocket upgrade logic via your preferred adapter
export default router;Building a server
morphis build --server=apiMorphis bundles src/routes/api.ts (and every module it imports) into a single optimised file:
dist/
api/
index.js ← production bundleThe entry that gets bundled looks like this internally:
import router from './api';
const port = Number(process.env.PORT ?? 3000);
Bun.serve({
port,
reusePort: process.env.MULTI_THREAD === 'true',
fetch: router.handle.bind(router),
});You never have to write that boilerplate yourself — it is injected at build time and removed afterwards.
Per-server builds
Each server produces its own isolated bundle. Code that is only imported by ws.ts will never appear in dist/api/index.js, and vice versa.
morphis build --server=api # → dist/api/index.js
morphis build --server=ws # → dist/ws/index.jsUse morphis build:api / morphis build:ws (the generated npm scripts) as shortcuts.
Running in production
# Start api server (reads from .env.api)
morphis start --server=api
# Or run the bundle directly
bun dist/api/index.jsProject layout with multiple servers
- .env.api
- .env.ws