A TypeScript-first mock server library built on top of unjs/h3, providing an elegant and type-safe way to create HTTP mock servers for development and testing.
Warning
This project is deprecated and no longer maintained. Please use kaivo instead.
- π― Type-Safe: Full TypeScript support with comprehensive type definitions
- π Built on H3: Leverages the powerful and minimal H3 framework
- π¨ Elegant API: Clean and intuitive configuration syntax
- π§ Flexible Routing: Support for nested routes and multiple HTTP methods
- π Middleware Support: Easy middleware registration with route-specific options
- π§© Plugin System: Extensible through H3's plugin architecture
- π¦ Zero Config: Works out of the box with sensible defaults
npm install better-mock-server h3import { createAppServer } from 'better-mock-server'
const server = createAppServer({
port: 3000,
routes: {
'/api/hello': (event) => {
return { message: 'Hello World!' }
}
}
})
await server.listen()
console.log(`Server running at ${server.url}`)
// Later: close the server
await server.close()// Use port 0 for automatic port assignment
const server = createAppServer({
port: 0,
routes: {
'/api/ping': () => 'pong'
}
})
await server.listen()
console.log(`Server running at ${server.url}`) // e.g., http://localhost:54321/
console.log(`Port: ${server.port}`) // e.g., 54321Routes define the HTTP endpoints and their handlers. You can use simple handlers or detailed route configurations.
const routes = {
'/api/ping': (event) => 'pong'
}const routes = {
'/api/users': {
GET: (event) => [
{ id: 1, name: 'John' },
{ id: 2, name: 'Jane' }
],
POST: async (event) => {
const body = await readBody(event)
return { id: 3, ...body }
},
DELETE: (event) => {
return { success: true }
}
}
}const routes = {
'/api': {
GET: (event) => 'API Root',
children: {
'/users': {
GET: (event) => 'List users',
children: {
'/:id': {
GET: (event) => `Get user ${event.context.params.id}`,
DELETE: (event) => `Delete user ${event.context.params.id}`
}
}
}
}
}
}const routes = {
'/api/meta': {
GET: {
handler: (event) => 'meta options',
options: {
meta: { name: 'king3' }
}
}
}
}Middlewares are functions that run before route handlers, useful for logging, authentication, CORS, etc.
const middlewares = [
(event, next) => {
console.log(`${event.method} ${event.path}`)
return next()
}
]const middlewares = [
{
route: '/api',
handler: (event, next) => {
console.log('API route accessed')
return next()
}
}
]const middlewares = [
{
handler: (event, next) => next(),
options: {
method: 'POST'
}
}
]Plugins extend the functionality of your server using H3's plugin system.
import { definePlugin } from 'better-mock-server'
const loggerPlugin = definePlugin((h3, _options) => {
if (h3.config.debug) {
h3.use((req) => {
console.log(`[${req.method}] ${req.url}`)
})
}
})
const server = createAppServer({
routes: {
/* ... */
},
plugins: [loggerPlugin]
})
await server.listen()Creates an HTTP server with the configured application.
Parameters:
options.routes(required): Routes configurationoptions.middlewares(optional): Middlewares arrayoptions.plugins(optional): Plugins arrayoptions.port(optional): Port number (default: 0 for random port)options.hostname(optional): Hostname (default: 'localhost')options.protocol(optional): Protocol (default: 'http')
Returns: AppServer object
AppServer Properties:
raw: Raw H3 server instanceapp: H3 application instanceport: Server port number (available afterlisten())url: Server URL (available afterlisten())listen(port?): Async function to start the server. Auto-closes the previous server if called againclose(): Async function to close the serverrestart(port?): Async function to restart the server. Uses the last listen port if no port is provided
Examples:
const server = createAppServer({
port: 3000,
routes: {
'/api/hello': () => 'Hello'
}
})
await server.listen()
console.log(`Running at ${server.url}`)
// Or override port when listening
await server.listen(4000)
// Clean up
await server.close()Creates an H3 application instance without starting a server. Useful when you want to integrate with existing server setup.
Parameters:
options.routes(optional): Routes configurationoptions.middlewares(optional): Middlewares arrayoptions.plugins(optional): Plugins array
Returns: H3 application instance
Example:
import { createApp } from 'better-mock-server'
import { serve } from 'h3'
const app = createApp({
routes: {
'/api/hello': () => 'Hello'
}
})
// Use with your own server configuration
const server = serve(app, { port: 4000 })
await server.ready()
console.log(`Server running at ${server.url}`)Provides type-safe route definitions with IDE auto-completion.
Example:
import { defineRoutes } from 'better-mock-server'
const routes = defineRoutes({
'/api/users': {
GET: () => [],
POST: async (event) => {
const body = await readBody(event)
return body
}
}
})Parses nested route structures into a flat array of route definitions. Mainly for internal use.
Parameters:
routes: Routes configuration objectbasePath(optional): Base path for nested routes
Returns: Array of parsed route objects
Registers routes to an H3 application instance.
Parameters:
app: H3 application instanceroutes(optional): Routes configuration
Defines middleware with type safety. Accepts either a function or configuration object.
Example:
import { defineMiddleware } from 'better-mock-server'
// With function
const mw1 = defineMiddleware((event, next) => {
console.log('Middleware')
return next()
})
// With config
const mw2 = defineMiddleware({
route: '/api',
handler: (event, next) => next(),
options: { method: 'POST' }
})Parses middleware configurations into standardized tuple format. Mainly for internal use.
Parameters:
middlewares: Array of middleware functions or configurations
Returns: Array of parsed middleware tuples
Registers middlewares to an H3 application instance.
Parameters:
app: H3 application instancemiddlewares(optional): Middlewares array
Re-export of H3's definePlugin for convenience.
Example:
import { definePlugin } from 'better-mock-server'
const myPlugin = definePlugin((h3, _options) => {
// Plugin setup
})Registers plugins to an H3 application instance.
Parameters:
app: H3 application instanceplugins(optional): Plugins array
Builds a server URL string from protocol, hostname, and optional port. Automatically normalizes the protocol (adds ':' if not present).
Parameters:
protocol: Protocol string (e.g., 'http', 'https', 'http:', 'https:')hostname: Hostname or IP addressport(optional): Port number or string
Returns: Full URL string
Examples:
import { buildServerUrl } from 'better-mock-server'
buildServerUrl('http', 'localhost', 3000)
// 'http://localhost:3000/'
buildServerUrl('https:', 'example.com', 443)
// 'https://example.com:443/'
buildServerUrl('http', '127.0.0.1')
// 'http://127.0.0.1/'
buildServerUrl('http', '::1', 8080)
// 'http://[::1]:8080/'Joins multiple path segments into a normalized path.
Example:
import { joinPaths } from 'better-mock-server'
joinPaths('/api', 'users') // '/api/users'
joinPaths('/api/', '/users/') // '/api/users'
joinPaths('api', '', 'users') // 'api/users'import type { EventHandler, RouteOptions } from 'h3'
type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE'
type AllHTTPMethod = 'ALL'
interface RouteHandlerConfig {
handler: EventHandler
options?: RouteOptions
}
type RouteHandler = EventHandler | RouteHandlerConfig
interface RouteConfig {
GET?: RouteHandler
POST?: RouteHandler
PUT?: RouteHandler
PATCH?: RouteHandler
DELETE?: RouteHandler
children?: Routes
}
interface Routes {
[route: string]: RouteHandler | RouteConfig
}
interface ParsedRoute {
route: string
method: HTTPMethod | AllHTTPMethod
handler: EventHandler
options?: RouteOptions
}import type { Middleware, MiddlewareOptions } from 'h3'
interface MiddlewareConfig {
route?: string
handler: Middleware
options?: MiddlewareOptions
}
type Middlewares = Array<Middleware | MiddlewareConfig>
type ParsedMiddleware =
| [Middleware]
| [string, Middleware]
| [Middleware, MiddlewareOptions]
| [string, Middleware, MiddlewareOptions]import type { H3Plugin } from 'h3'
type Plugins = H3Plugin[]import type { H3 as H3Instance, serve } from 'h3'
import type { ServerOptions } from 'srvx'
type Server = ReturnType<typeof serve>
type App = H3Instance
interface AppOptions {
routes?: Routes
middlewares?: Middlewares
plugins?: Plugins
}
type srvxServerOptions = Omit<ServerOptions, 'fetch' | 'middleware' | 'plugins'>
interface AppServerOptions extends AppOptions, srvxServerOptions {
routes: Routes
}
interface AppServer {
raw: Server | undefined
app: App
port: number | string | undefined
url: string | undefined
listen: (listenPort?: number) => Promise<void>
close: () => Promise<void>
restart: (listenPort?: number) => Promise<void>
}import {
createAppServer,
defineMiddleware,
definePlugin
} from 'better-mock-server'
import { readBody } from 'h3'
// Define a logger middleware
const logger = defineMiddleware((event, next) => {
console.log(`[${new Date().toISOString()}] ${event.method} ${event.path}`)
return next()
})
// Define a custom plugin
const corsPlugin = definePlugin((h3, _options) => {
// CORS setup logic
})
// Create server with full configuration
const server = createAppServer({
port: 3000,
plugins: [corsPlugin],
middlewares: [
logger,
{
route: '/api',
handler: (event, next) => {
event.context.apiAccess = true
return next()
}
}
],
routes: {
'/': () => 'Welcome to Better Mock Server!',
'/api': {
GET: () => ({ version: '1.0.0' }),
children: {
'/users': {
GET: () => [
{ id: 1, name: 'Alice', email: 'alice@example.com' },
{ id: 2, name: 'Bob', email: 'bob@example.com' }
],
POST: async (event) => {
const body = await readBody(event)
return {
id: Date.now(),
...body,
createdAt: new Date().toISOString()
}
},
children: {
'/:id': {
GET: (event) => {
const id = event.context.params.id
return {
id,
name: `User ${id}`,
email: `user${id}@example.com`
}
},
PUT: async (event) => {
const id = event.context.params.id
const body = await readBody(event)
return {
id,
...body,
updatedAt: new Date().toISOString()
}
},
DELETE: (event) => {
const id = event.context.params.id
return {
success: true,
deletedId: id
}
}
}
}
},
'/posts': {
GET: () => [
{ id: 1, title: 'First Post', content: 'Hello World' },
{ id: 2, title: 'Second Post', content: 'TypeScript is awesome' }
]
}
}
}
}
})
await server.listen()
console.log(`π Server running at ${server.url}`)
// Graceful shutdown
process.on('SIGINT', async () => {
console.log('\nπ Shutting down...')
await server.close()
process.exit(0)
})Let the system assign an available port automatically:
const server = createAppServer({
port: 0, // Random port
routes: {
/* ... */
}
})
await server.listen()
console.log(`Test server running on port ${server.port}`)Always wrap your routes with defineRoutes() for better IDE support and type checking.
Middlewares and routes are registered in the order they appear. Place global middlewares before route-specific ones.
When working with request bodies or async operations, always use async handlers:
;async (event) => {
const body = await readBody(event)
return body
}Use H3's error handling utilities:
import { createError } from 'h3'
;(event) => {
throw createError({
statusCode: 404,
message: 'User not found'
})
}Access route parameters through event.context.params:
const routes = {
'/:id': {
GET: (event) => {
const id = event.context.params.id
return { id }
}
}
}Use the children property for better organization:
const routes = {
'/api': {
children: {
'/users': {
/* ... */
},
'/posts': {
/* ... */
}
}
}
}- The library is built on H3, so all H3 limitations apply
- Route definitions must be known at server startup (no dynamic route registration)
- Middleware execution order follows the registration order
- Port 0 will assign a random available port
MIT License Β© 2025-PRESENT king3
Contributions, issues and feature requests are welcome!
Feel free to check the issues page.