better-mock-server
v1.4.1
Published
A lightweight and fast mock server built on unjs/h3 for modern web development.
Downloads
26
Readme
better-mock-server
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.
✨ Features
- 🎯 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
📦 Installation
npm install better-mock-server h3🚀 Quick Start
Basic Usage
import { 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()Random Port
// 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., 54321🎯 Core Concepts
Routes
Routes define the HTTP endpoints and their handlers. You can use simple handlers or detailed route configurations.
Simple Handler (All Methods)
const routes = {
'/api/ping': (event) => 'pong'
}Method-Specific Handlers
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 }
}
}
}Nested Routes
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}`
}
}
}
}
}
}Route Options
const routes = {
'/api/meta': {
GET: {
handler: (event) => 'meta options',
options: {
meta: { name: 'king3' }
}
}
}
}Middlewares
Middlewares are functions that run before route handlers, useful for logging, authentication, CORS, etc.
Global Middleware
const middlewares = [
(event, next) => {
console.log(`${event.method} ${event.path}`)
return next()
}
]Route-Specific Middleware
const middlewares = [
{
route: '/api',
handler: (event, next) => {
console.log('API route accessed')
return next()
}
}
]Middleware with Options
const middlewares = [
{
handler: (event, next) => next(),
options: {
method: 'POST'
}
}
]Plugins
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()📚 API Reference
Server Functions
createAppServer(options)
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()createApp(options)
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}`)Route Functions
defineRoutes(routes)
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
}
}
})parseRoutes(routes, basePath?)
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
registerRoutes(app, routes?)
Registers routes to an H3 application instance.
Parameters:
app: H3 application instanceroutes(optional): Routes configuration
Middleware Functions
defineMiddleware(input)
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' }
})parseMiddlewares(middlewares)
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
registerMiddlewares(app, middlewares?)
Registers middlewares to an H3 application instance.
Parameters:
app: H3 application instancemiddlewares(optional): Middlewares array
Plugin Functions
definePlugin
Re-export of H3's definePlugin for convenience.
Example:
import { definePlugin } from 'better-mock-server'
const myPlugin = definePlugin((h3, _options) => {
// Plugin setup
})registerPlugins(app, plugins?)
Registers plugins to an H3 application instance.
Parameters:
app: H3 application instanceplugins(optional): Plugins array
Utility Functions
buildServerUrl(protocol, hostname, port?)
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/'joinPaths(...paths)
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'📝 Type Definitions
Routes Types
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
}Middleware Types
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]Plugin Types
import type { H3Plugin } from 'h3'
type Plugins = H3Plugin[]Server Types
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>
}💡 Complete Example
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: '[email protected]' },
{ id: 2, name: 'Bob', email: '[email protected]' }
],
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)
})✅ Best Practices
1. Use Port 0 for Testing
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}`)2. Use defineRoutes for Type Safety
Always wrap your routes with defineRoutes() for better IDE support and type checking.
3. Order Matters
Middlewares and routes are registered in the order they appear. Place global middlewares before route-specific ones.
4. Async Handlers
When working with request bodies or async operations, always use async handlers:
;async (event) => {
const body = await readBody(event)
return body
}5. Error Handling
Use H3's error handling utilities:
import { createError } from 'h3'
;(event) => {
throw createError({
statusCode: 404,
message: 'User not found'
})
}6. Path Parameters
Access route parameters through event.context.params:
const routes = {
'/:id': {
GET: (event) => {
const id = event.context.params.id
return { id }
}
}
}7. Nested Routes
Use the children property for better organization:
const routes = {
'/api': {
children: {
'/users': {
/* ... */
},
'/posts': {
/* ... */
}
}
}
}⚠️ Constraints & Limitations
- 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
📄 License
MIT License © 2025-PRESENT king3
🤝 Contributing
Contributions, issues and feature requests are welcome!
Feel free to check the issues page.
