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:
- Namespace: Logically divides your API (e.g.,
core
,furniture
). - Service: Represents a specific function (e.g.,
profile
,product
). - Method: HTTP method (
get
,post
,put
,delete
,patch
). - 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
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:
pnpm silgi install
After installation, run the silgi prepare
command to build your schemas and services:
pnpm silgi prepare
1. Define Namespaces
Declare the namespaces you will use in your silgi.config.ts
file:
import { defineSilgiConfig } from 'silgi/config'
export default defineSilgiConfig({
namespaces: ['core', 'furniture'],
})
To update types and runtime files:
pnpm silgi prepare
2. Define Your Schema
You can define schemas under server/schemas/
or server/services/
.
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:
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
.
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.
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:
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
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
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.
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:
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.