Skip to content

Schemas in Silgi

In Silgi, schemas define the data contract and validation rules for your API. Schemas created with createSchema greatly enhance both developer experience and application reliability.


Why Use Schemas?

  • End-to-End Type Safety: Full type control with TypeScript.
  • Automatic Validation: Incoming and outgoing data is validated automatically.
  • Self-Documenting API: Schemas provide automatic API documentation.
  • Modularity & Reuse: Keep schemas in separate files and reuse them as needed.
  • Enhanced Developer Experience: IDE support, autocompletion, and fast error catching.
  • Automatic Binding: Schemas are automatically discovered and bound by the Silgi CLI.
  • Extensibility: Modules can add new parameters to schemas.

Schema Basics

Schemas in Silgi define the structure and validation rules for your API's data. Each schema consists of four main parts:

  1. Namespace: Logically divides your API (e.g., core, furniture).
  2. Service: Represents a specific function (e.g., profile, product).
  3. Method: HTTP method (get, post, put, delete, patch).
  4. Action: A specific operation for that service/method (e.g., byId, create).

INFO

Schemas in Silgi are not limited to your own definitions. Modules can dynamically extend schemas by adding new types, parameters, or validation rules.

URI Example:core/profile/get/byId or furniture/product/get/details


Schema Parameters

For each endpoint, you can define the following parameters:

  • input: Request body.
  • output: Response data.
  • pathParams: URL path parameters (e.g., /core/profile/:profileId).
  • queryParams: URL query parameters (e.g., ?includeDetails=true).
  • Additional Parameters: Can be added by modules.

Supported Validation Libraries

Silgi supports Standard Schema compatible libraries.


Step-by-Step Schema Creation

0. Installation

silgi.config.ts
typescript
import { defineSilgiConfig } from 'silgi/config'

export default defineSilgiConfig({
  // Default: zod.
  // It's possible to use multiple validation libraries in one project.
  // For a single library: 'zod'
  // For multiple libraries: ['arktype', 'zod', 'valibot']
  schemaVendor: 'zod',
  // ... other settings
})

Run the following command to install packages and dependencies:

bash
pnpm silgi install

After installation, run the silgi prepare command to build your schemas and services:

bash
pnpm silgi prepare

1. Define Namespaces

Declare the namespaces you will use in your silgi.config.ts file:

silgi.config.ts
typescript
import { defineSilgiConfig } from 'silgi/config'

export default defineSilgiConfig({
  namespaces: ['core', 'furniture'],
})

To update types and runtime files:

bash
pnpm silgi prepare

2. Define Your Schema

You can define schemas under server/schemas/ or server/services/.

server/schemas/profile.schema.ts
typescript
import { createSchema } from 'silgi'
import { z } from 'zod'

export const profileSchema = createSchema({
  core: {
    profile: {
      get: {
        byId: {
          input: z.object({}),
          output: z.object({
            id: z.string().uuid(),
            name: z.string(),
            email: z.string().email(),
          }),
          pathParams: z.object({
            profileId: z.string().uuid()
          }),
          queryParams: z.object({
            includeDetails: z.boolean().optional()
          })
        }
      }
    }
  }
})

INFO

Your exported schema must start with export const. Otherwise, Silgi CLI will not find your schema.

To update types and runtime files again:

bash
pnpm silgi prepare

3. Automatic Schema-Service Binding

Schemas and services are defined in separate files. Silgi automatically matches schemas and services based on their URI structure. You do not need to manually pass the schema to createService.

server/services/profile.service.ts
typescript
import { createService } from 'silgi'

export const profileService = createService({
  core: {
    profile: {
      get: {
        byId: {
          handler: async (input, shared) => {
            const { profileId, includeDetails } = input.parameters
            // ...database operations...
            return {
              id: profileId,
              name: 'Test Profile',
              email: '[email protected]'
            }
          }
        }
      }
    }
  }
})

4. Accessing Parameters in the Handler

In your handler function:

  • input: Validated request body.
  • input.parameters: Combined pathParams and queryParams.
  • shared: Shared helpers (e.g., database).
  • event: Framework-specific event object.
  • source: Request source.
typescript
handler: async (input, shared) => {
  const { profileId, includeDetails } = input.parameters
  // ...business logic...
  return { /* validated response */ }
}

What to Do When You Change a Schema?

When you make a change to your schema, update Silgi's types and runtime files by running:

bash
pnpm silgi prepare

FAQ

Can I use different validation libraries?

Yes, support for other Standard Schema compatible libraries besides Zod is being developed.

Can modules or plugins extend my schemas?

Yes, modules and plugins can add new parameters or validation rules to schemas.

Are schemas automatically bound?

Yes, schemas and services with the same URI structure are automatically matched.


Summary

With Silgi schemas, you can easily manage your API's data contract, validation, and type safety. Thanks to automatic binding, modularity, and strong validation, building fast and reliable backend services is now much easier!


Advanced Example: Order Detail Service

Below is an example showing how to use advanced features on both the schema and service sides.

1. Schema Definition

server/schemas/order.schema.ts
typescript
import { createSchema } from 'silgi'
import { z } from 'zod'

const OrderItemSchema = z.object({
  productId: z.string().uuid(),
  name: z.string(),
  quantity: z.number().int().min(1),
  price: z.number().min(0)
})

const OrderSchema = z.object({
  id: z.string().uuid(),
  userId: z.string().uuid(),
  status: z.enum(['pending', 'completed', 'cancelled']),
  items: z.array(OrderItemSchema),
  total: z.number().min(0),
  createdAt: z.string().datetime()
})

export const orderSchema = createSchema({
  commerce: {
    order: {
      get: {
        detail: {
          input: z.object({}),
          output: OrderSchema,
          pathParams: z.object({
            orderId: z.string().uuid()
          }),
          queryParams: z.object({
            includeUser: z.boolean().optional()
          })
        }
      }
    }
  }
})

2. Service Definition

server/services/order.service.ts
typescript
import { createService, ErrorFactory } from 'silgi'

export const orderService = createService({
  commerce: {
    order: {
      get: {
        detail: {
          handler: async (input, shared) => {
            const { orderId, includeUser } = input.parameters

            // Fetch order from the database
            const order = await shared.db.getOrderById(orderId)
            if (!order) {
              throw ErrorFactory.notFound('Order not found')
            }

            // Optionally include user info
            let user
            if (includeUser) {
              user = await shared.db.getUserById(order.userId)
            }

            // Build response
            return {
              ...order,
              ...(user ? { user } : {})
            }
          }
        }
      }
    }
  }
})

3. Shared Helper

Create a shared helper to centrally manage database operations. All services can automatically access this helper.

server/shared/db.ts
typescript
import { createShared } from 'silgi'
import type { ExtendShared } from 'silgi/types'
import { useDatabase } from '../utils/db'

export interface SharedDatabase extends ExtendShared {
  database: ReturnType<typeof useDatabase>
}

export const db = createShared({
  database: useDatabase,
})

Note

Your exported shared helpers must start with export const. Otherwise, Silgi CLI will not find your helper.

TIP

After writing shared code, don't forget to run:

bash
pnpm silgi prepare

In this example:

  • Schema: Structured for order details, related products, and optional user info.
  • Service: Uses both path and query parameters, applies error management, and accesses the shared helper.
  • Shared: Provides central helper functions for database operations.

You can define complex, type-safe, modular, and readable API endpoints like this in Silgi for real-world scenarios.

Released under the MIT License. (dev). Documentation design is a copy of vite.dev docs.