Vision

Quickstart

Get Vision Server up and running in under 5 minutes

Quickstart

Get Vision Server up and running in under 5 minutes.

Installation

bun add @getvision/server zod

Already using Hono or Fastify? Check out Adapters to add Vision to your existing app.

Basic Setup

Create your first Vision Server app:

src/index.ts
import { createVision, createModule } from '@getvision/server'
import { z } from 'zod'

const usersModule = createModule({ prefix: '/users' })
  .get(
    '/:id',
    ({ params }) => ({
      id: params.id,
      name: 'John Doe',
      email: '[email protected]',
    }),
    {
      params: z.object({ id: z.string() }),
      response: z.object({
        id: z.string(),
        name: z.string(),
        email: z.string(),
      }),
    }
  )

const app = createVision({
  service: {
    name: 'My API',
    version: '1.0.0',
    description: 'My awesome API',
  },
}).use(usersModule)

app.listen(3000)

/** Eden Treaty type for typed RPC on the client. */
export type AppType = typeof app

That's it! Start your app and visit:

Composing Modules

Modules are the building blocks. Each one lives in its own file, owns a path prefix, and is composed at the root via .use():

src/modules/users.ts
import { createModule } from '@getvision/server'
import { z } from 'zod'

export const usersModule = createModule({ prefix: '/users' })
  .get('/', () => ({ users: [] }))
  .post(
    '/',
    async ({ body }) => ({ id: crypto.randomUUID(), ...body }),
    { body: z.object({ name: z.string(), email: z.string().email() }) }
  )
src/index.ts
import { createVision } from '@getvision/server'
import { usersModule } from './modules/users'
import { ordersModule } from './modules/orders'
import { productsModule } from './modules/products'

const app = createVision({ service: { name: 'My API' } })
  .use(usersModule)
  .use(ordersModule)
  .use(productsModule)

app.listen(3000)

export type AppType = typeof app

Learn more about modules →

With Database Tracing

Every handler receives span() for tracing arbitrary blocks:

import { db } from './db'
import { users } from './db/schema'
import { eq } from 'drizzle-orm'

export const usersModule = createModule({ prefix: '/users' })
  .get(
    '/:id',
    ({ params, span }) => {
      const user = span(
        'db.select',
        { 'db.system': 'sqlite', 'db.table': 'users' },
        () => db.select().from(users).where(eq(users.id, params.id)).get()
      )
      return user
    },
    { params: z.object({ id: z.string() }) }
  )

Vision Dashboard will show:

  • Request → Handler → DB Query waterfall
  • Query timing and attributes
  • Full trace correlation

With Pub/Sub Events

Use defineEvents() to register typed event handlers, then emit() from any route in the same app:

import { createModule, defineEvents } from '@getvision/server'
import { z } from 'zod'

export const usersModule = createModule({ prefix: '/users' })
  .use(
    defineEvents({
      'user/created': {
        schema: z.object({
          userId: z.string(),
          email: z.string().email(),
        }),
        handler: async (event) => {
          console.log('Send welcome email to:', event.email)
        },
      },
    })
  )
  .post(
    '/',
    async ({ body, emit }) => {
      const user = { id: crypto.randomUUID(), ...body }
      await emit('user/created', { userId: user.id, email: user.email })
      return user
    },
    { body: z.object({ name: z.string(), email: z.string().email() }) }
  )

Pub/sub is powered by BullMQ — in-memory during development (pubsub: { devMode: true }), Redis-backed in production.

Eden Treaty Client

On the frontend, import the exported AppType and get a fully-typed RPC client — no codegen:

frontend/api.ts
import { treaty } from '@elysia/eden'
import type { AppType } from '../server'

const api = treaty<AppType>('http://localhost:3000')

const { data } = await api.users({ id: '1' }).get()
//         ^? { id: string; name: string; email: string }

With Drizzle Studio

Auto-start Drizzle Studio alongside your app:

const app = createVision({
  service: {
    name: 'My API',
    integrations: {
      database: 'sqlite://./dev.db',
    },
    drizzle: {
      autoStart: true, // Auto-start Drizzle Studio
      port: 4983,
    },
  },
})

Vision will:

  • ✅ Detect drizzle.config.ts
  • ✅ Start Drizzle Studio on port 4983
  • ✅ Show a link in Dashboard → Integrations

Configuration

Full configuration options:

const app = createVision({
  service: {
    name: 'My API',
    version: '1.0.0',
    description: 'Optional description',
    integrations: {
      database: 'postgresql://localhost/mydb',
      redis: 'redis://localhost:6379',
    },
    drizzle: {
      autoStart: true,
      port: 4983,
    },
  },
  vision: {
    enabled: true,   // enable/disable dashboard
    port: 9500,      // dashboard port
    maxTraces: 1000,
    maxLogs: 10000,
  },
  pubsub: {
    devMode: true,   // in-memory queue, no Redis required
    // redis: { host: 'localhost', port: 6379 }
  },
})

Next Steps