Skip to content

Latest commit

 

History

History
169 lines (133 loc) · 3.69 KB

File metadata and controls

169 lines (133 loc) · 3.69 KB

Quick Start Guide

quickstart, getting-started, tutorial

Get started with @adi-utils/http in 5 minutes.

Step 1: Install Dependencies

# Core packages
bun add @adi-utils/http @adi-utils/http-express zod

# Dev dependencies
bun add -D @types/express express

Step 2: Create Project Structure

mkdir -p packages/{api-contracts,backend/handlers,client/api}

Step 3: Define Your First Config

// 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

Step 4: Implement Server Handler

// 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
})

Step 5: Serve with Express

// 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')
})

Step 6: Use in Client

// 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!

Next Steps

  • Check out README.md for complete documentation
  • See examples/ for more complex use cases
  • Learn about error handling, middleware, and validation

Common Patterns

POST with Body

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 HandlerConfig

GET with Query Params

export 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 HandlerConfig

Nested Routes

export 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

Tips

  1. Always use as const satisfies HandlerConfig for proper type inference
  2. Export configs from @api-contracts for sharing between client/server
  3. Use route.static() for fixed paths - no parameters needed
  4. Use route.pattern() for dynamic routes - Express-style patterns with Zod validation
  5. Validate body and query - use Zod schemas for safety
  6. Keep configs simple - no complex logic in configs