Silgi

Core Concepts

The mental model behind Silgi — context, procedures, middleware, and type inference.

Before diving into API details, it helps to understand how Silgi thinks about your code. There are four main ideas.

Context

Every request starts by running your context factory. This is a function you provide when creating the Silgi instance. It receives the raw HTTP request and returns an object.

import {  } from 'silgi'

const  = ({
  : () => ({
    : getDB(),
    : .(.),
  }),
})

This object — called ctx — is available in every procedure and every guard. Think of it as the shared state for one request.

Guards can add to the context. If a guard returns { user }, then ctx.user becomes available in all procedures that use that guard.

Procedures

A procedure is a single API function. It takes an input, does something, and returns an output. All procedures default to POST. Use .$route({ method: 'GET' }) for cacheable reads, and subscription() for streaming.

TypeWhat it does
procedureHandles a request and returns a response. Default method is POST. Use .$route({ method: 'GET' }) for reads.
subscriptionStreams data over time. Uses Server-Sent Events.

See the Procedures page for how to write them.

Middleware

Middleware runs before (and sometimes after) a procedure. Silgi has two kinds:

Guards run before the procedure and can add to the context:

const  = s.guard(async () => {
  const  = await verifyToken(.headers.authorization)
  if (!) throw new SilgiError('UNAUTHORIZED')
  return {  } // added to ctx
})

Wraps run before and after, like an onion:

const  = s.wrap(async (, ) => {
  const  = .()
  const  = await ()
  .(`Took ${.() - }ms`)
  return 
})

See the Middleware page for details and execution order.

Type inference

Silgi infers types from your code. You don't write them yourself and you don't run a code generator.

When you define a procedure with a Zod schema, the client automatically knows the input type. When the procedure returns something, the client knows the return type. When a guard adds { user } to the context, the procedure knows ctx.user exists.

This works because of InferClient:

import type {  } from 'silgi'

type  = <typeof appRouter>
// Client.users.list: (input: { limit?: number }) => Promise<User[]>

Silgi uses the Standard Schema spec for validation. This means Zod, Valibot, and ArkType all work — you're not locked into any one library.

On this page