Docs /backend

Backend Documentation

Hono + tRPC API service

๐Ÿ“‹ Overview

The Izri backend is a modern, type-safe API server built with Hono (fast web framework) and tRPC (type-safe RPC), using Drizzle ORM for database access. This provides end-to-end type safety from database to frontend with excellent performance.

๐ŸŽฏ Technology Stack

  • Framework: Hono (Fast, lightweight web server)
  • API Layer: tRPC (Type-safe Remote Procedure Calls)
  • Database ORM: Drizzle ORM (TypeScript-first ORM)
  • Database: PostgreSQL 15
  • Cache: Redis 7
  • Validation: Zod schemas
  • Logging: Pino (structured logging)
  • Observability: OpenTelemetry (optional)
  • Runtime: Node.js 18+ with tsx for development

๐Ÿ“š Documentation Pages

API Server Setup

Complete API server configuration guide

  • Server architecture overview
  • Quick start and installation
  • Environment configuration
  • Hono + tRPC integration
  • OpenTelemetry setup
  • Docker deployment
  • Testing and debugging

tRPC Server

tRPC implementation details

  • Router structure
  • Procedure types (query vs mutation)
  • Context creation
  • Middleware implementation
  • Input validation
  • Type inference
  • Client integration

Authentication

Backend authentication strategies

  • Authentication architecture
  • Session management
  • API tokens
  • OAuth integration
  • Authorization patterns
  • Security best practices

Database Schema

Complete database reference

  • Full schema documentation
  • Table relationships
  • Indexes and constraints
  • Enums and types
  • Query patterns
  • Performance optimization

Migrations

Database migration workflow

  • Creating migrations
  • Applying migrations
  • Rolling back changes
  • Migration best practices
  • Schema versioning
  • Production migration strategy

Error Handling

Error management patterns

  • Error types and codes
  • Error transformation
  • Client error handling
  • Logging errors
  • User-facing errors
  • Debugging strategies

Logging

Structured logging and observability

  • Pino configuration
  • Log levels and structure
  • Request logging
  • Error logging
  • Performance logging
  • OpenTelemetry integration
  • Log aggregation

๐Ÿ—๏ธ API Structure

apps/api/
โ””โ”€โ”€ src/
    โ””โ”€โ”€ index.ts              # Hono server entry point

packages/trpc/
โ”œโ”€โ”€ src/
โ”‚   โ”œโ”€โ”€ context.ts            # tRPC context (auth, db)
โ”‚   โ”œโ”€โ”€ routers/
โ”‚   โ”‚   โ””โ”€โ”€ index.ts          # All tRPC procedures
โ”‚   โ””โ”€โ”€ index.ts              # Exports
โ””โ”€โ”€ package.json

packages/database/
โ”œโ”€โ”€ src/
โ”‚   โ”œโ”€โ”€ schema.ts             # Drizzle schema definitions
โ”‚   โ”œโ”€โ”€ client.ts             # Database client
โ”‚   โ”œโ”€โ”€ seed.ts               # Seed script
โ”‚   โ””โ”€โ”€ index.ts              # Exports
โ”œโ”€โ”€ migrations/               # SQL migration files
โ””โ”€โ”€ drizzle.config.ts         # Drizzle configuration

๐Ÿš€ Quick Start

Development

# Start API server
pnpm dev:api

# Or from root
pnpm dev

# Access at http://localhost:4000

Testing Endpoints

# Health check (REST)
curl http://localhost:4000/health

# Health check (tRPC)
curl -X POST http://localhost:4000/trpc/health

# Get projects (tRPC)
curl -X POST http://localhost:4000/trpc/projects.getProjects \
  -H "Content-Type: application/json" \
  -d '{"userEmail":"test@izri.com"}'

๐Ÿ“ก API Endpoints

REST Endpoints

Method Path Description
GET /health Health check
POST /trpc/* tRPC endpoint

tRPC Procedures

Queries (Read Operations)

Procedure Input Output Description
health - { ok: boolean } API health check
projects.getProjects { userEmail } Project[] Get user's projects
projects.getProject { projectId, userEmail } Project Get single project
organizations.list { userEmail } Organization[] List user's organizations
auth.getSession - SessionInfo Get current session

Mutations (Write Operations)

Procedure Input Output Description
projects.createProject CreateProjectInput Project Create new project
projects.updateProject UpdateProjectInput Project Update project
projects.deleteProject { projectId, userEmail } { message } Delete project
organizations.create CreateOrganizationInput Organization Create organization
analysis.analyzeRepository AnalyzeRepositoryInput AnalysisResult Analyze repository

๐Ÿ—„๏ธ Database Schema

Core Tables

  • users: User accounts and profiles
  • organizations: Teams and workspaces
  • projects: Repository projects
  • test_runs: Test execution records
  • project_settings: Project configuration
  • organization_members: Team memberships
  • api_keys: API authentication tokens

Relationships

users โ”€โ”€< projects โ”€โ”€< test_runs
  โ”‚        โ”‚
  โ”‚        โ””โ”€ project_settings
  โ”‚
  โ””โ”€< organization_members >โ”€ organizations

๐Ÿ” Authentication Flow

graph LR
    A[Client] --> B{Auth Type}
    B -->|Session| C[Cookie]
    B -->|API Token| D[Bearer Token]
    C --> E[Validate Session]
    D --> F[Validate Token]
    E --> G[User Context]
    F --> G
    G --> H[tRPC Procedures]

๐Ÿ“ Example Implementation

tRPC Procedure

// packages/trpc/src/routers/index.ts
import { z } from 'zod'
import { publicProcedure, router } from '../trpc'
import { projects } from '@izri/database'
import { eq } from 'drizzle-orm'

export const appRouter = router({
  projects: router({
    // Query example
    getProject: protectedProcedure
      .input(z.object({
        projectId: z.string(),
        userEmail: z.string().email()
      }))
      .query(async ({ ctx, input }) => {
        const project = await ctx.db.query.projects.findFirst({
          where: eq(projects.id, input.projectId),
          with: {
            testRuns: {
              orderBy: (testRuns, { desc }) => [desc(testRuns.createdAt)],
              limit: 10
            }
          }
        })

        if (!project) {
          throw new TRPCError({
            code: 'NOT_FOUND',
            message: 'Project not found'
          })
        }

        return project
      }),

    // Mutation example
    createProject: protectedProcedure
      .input(z.object({
        name: z.string(),
        description: z.string().optional(),
        repository: z.string().url(),
        branch: z.string().default('main'),
        language: z.string(),
        framework: z.string().optional(),
        userEmail: z.string().email()
      }))
      .mutation(async ({ ctx, input }) => {
        const [project] = await ctx.db
          .insert(projects)
          .values({
            ...input,
            userId: ctx.user.id
          })
          .returning()

        return project
      })
  })
})

Using from Frontend

// In React component
import { trpc } from '~/lib/trpc'

function ProjectDetail({ projectId }: { projectId: string }) {
  // Query - auto-typed!
  const { data: project, isLoading } = trpc.projects.getProject.useQuery({
    projectId,
    userEmail: user.email
  })

  // Mutation - auto-typed!
  const updateProject = trpc.projects.updateProject.useMutation({
    onSuccess: () => {
      // Handle success
    }
  })

  if (isLoading) return <div>Loading...</div>

  return (
    <div>
      <h1>{project.name}</h1>
      <p>{project.description}</p>
      {/* ... */}
    </div>
  )
}

๐Ÿ”ง Configuration

Environment Variables

# Database
DATABASE_URL="postgresql://postgres:password@localhost:5433/izri"

# API Server
API_PORT=4000
LOG_LEVEL="info"  # debug, info, warn, error

# OpenTelemetry (optional)
OTEL_EXPORTER_OTLP_ENDPOINT="http://localhost:4318/v1/traces"

# Authentication (future)
JWT_SECRET="your-secret-key"
GITHUB_CLIENT_ID="your-github-client-id"
GITHUB_CLIENT_SECRET="your-github-client-secret"

Hono Server Configuration

// apps/api/src/index.ts
import { Hono } from 'hono'
import { trpcServer } from '@hono/trpc-server'
import { appRouter } from '@izri/trpc'
import { createContext } from '@izri/trpc/context'

const app = new Hono()

// Health endpoint
app.get('/health', (c) => c.json({ ok: true }))

// tRPC endpoint
app.use(
  '/trpc/*',
  trpcServer({
    router: appRouter,
    createContext
  })
)

const port = process.env.API_PORT || 4000
console.log(`๐Ÿš€ Server running on http://localhost:${port}`)

export default app

๐Ÿ“Š Performance

Query Optimization

// Use indexes
await ctx.db.query.projects.findMany({
  where: eq(projects.userId, userId),
  // Fast due to index on userId
})

// Use select for large tables
await ctx.db
  .select({
    id: projects.id,
    name: projects.name
  })
  .from(projects)
  .where(eq(projects.userId, userId))

// Use with for relations
await ctx.db.query.projects.findFirst({
  with: {
    testRuns: true  // Efficient join
  }
})

Caching Strategy

// Redis caching (future)
const cacheKey = `project:${projectId}`
const cached = await redis.get(cacheKey)

if (cached) return JSON.parse(cached)

const project = await db.query.projects.findFirst({...})

await redis.set(cacheKey, JSON.stringify(project), 'EX', 3600)

๐Ÿงช Testing

// Test tRPC procedures
import { appRouter } from './routers'
import { createContext } from './context'

describe('projects.getProject', () => {
  it('should return project', async () => {
    const ctx = await createContext({})
    const caller = appRouter.createCaller(ctx)

    const project = await caller.projects.getProject({
      projectId: 'test-project-id',
      userEmail: 'test@example.com'
    })

    expect(project).toBeDefined()
    expect(project.id).toBe('test-project-id')
  })
})

๐Ÿ”— Related Documentation

๐Ÿ†˜ Troubleshooting

Server Won't Start

# Check if port is in use
lsof -ti:4000 | xargs kill -9

# Check database connection
psql $DATABASE_URL

# Check environment variables
cat .env | grep DATABASE_URL

Type Errors

# Rebuild packages
pnpm build:packages

# Generate database types
pnpm db:generate

Database Errors

# Check migrations
pnpm db:migrate

# Restart database
pnpm docker:down
pnpm dev:docker

Ready to dive deep? Start with API Overview โ†’

Reading this with an agent? /docs/backend.md serves the raw markdown.

All docs