quickstart, getting-started, tutorial
Get started with @adi-utils/http in 5 minutes.
# Core packages
bun add @adi-utils/http @adi-utils/http-express zod
# Dev dependencies
bun add -D @types/express expressmkdir -p packages/{api-contracts,backend/handlers,client/api}// packages/api-contracts/users.ts
import { z } from 'zod'
import { route } from '@adi-utils/http'
import type { HandlerConfig } from '@adi-utils/http'
export const getUserConfig = {
method: 'GET',
route: route.pattern('/api/users/:id', z.object({ id: z.string() })),
response: {
schema: z.object({
id: z.string(),
name: z.string(),
email: z.string().email()
})
}
} as const satisfies HandlerConfig// packages/backend/handlers/users.ts
import { handler } from '@adi-utils/http'
import { getUserConfig } from '@api-contracts/users'
export const getUserHandler = handler(getUserConfig, async (ctx) => {
// Your business logic here
const user = await db.users.findById(ctx.params.id)
if (!user) {
throw new Error('User not found')
}
return user
})// packages/backend/index.ts
import express from 'express'
import { serveExpress } from '@adi-utils/http-express'
import { getUserHandler } from './handlers/users'
const app = express()
app.use(express.json())
serveExpress(app, [getUserHandler])
app.listen(3000, () => {
console.log('Server running on http://localhost:3000')
})// packages/client/api/users.ts
import { BaseClient } from '@adi-utils/http'
import { getUserConfig } from '@api-contracts/users'
const client = new BaseClient({
baseUrl: 'http://localhost:3000'
})
// Fully type-safe!
const user = await client.run(getUserConfig, {
params: { id: '123' }
})
console.log(user.name) // TypeScript knows this exists!- Check out README.md for complete documentation
- See examples/ for more complex use cases
- Learn about error handling, middleware, and validation
export const createUserConfig = {
method: 'POST',
route: route.static('/api/users'),
body: {
schema: z.object({
name: z.string(),
email: z.string().email()
})
},
response: {
schema: z.object({ id: z.string() })
}
} as const satisfies HandlerConfigexport const listUsersConfig = {
method: 'GET',
route: route.static('/api/users'),
query: {
schema: z.object({
page: z.number().optional(),
limit: z.number().optional()
})
},
response: {
schema: z.array(z.object({ id: z.string(), name: z.string() }))
}
} as const satisfies HandlerConfigexport const getUserPostConfig = {
method: 'GET',
route: route.pattern(
'/api/users/:userId/posts/:postId',
z.object({
userId: z.string(),
postId: z.string()
})
),
response: {
schema: z.object({ id: z.string(), title: z.string() })
}
} as const satisfies HandlerConfig- Always use
as const satisfies HandlerConfigfor proper type inference - Export configs from
@api-contractsfor sharing between client/server - Use
route.static()for fixed paths - no parameters needed - Use
route.pattern()for dynamic routes - Express-style patterns with Zod validation - Validate body and query - use Zod schemas for safety
- Keep configs simple - no complex logic in configs