ワンクリックで
orpc-api
// Create and modify oRPC API routers, procedures, and middleware for the builder app. Use when adding API endpoints, creating routers, defining procedures, working with oRPC middleware, or building OpenAPI routes.
// Create and modify oRPC API routers, procedures, and middleware for the builder app. Use when adding API endpoints, creating routers, defining procedures, working with oRPC middleware, or building OpenAPI routes.
| name | orpc-api |
| description | Create and modify oRPC API routers, procedures, and middleware for the builder app. Use when adding API endpoints, creating routers, defining procedures, working with oRPC middleware, or building OpenAPI routes. |
/rpc) and OpenAPI (/api) endpoints{ headers, user?, workspace? }authorizedAPI (session) and workspaceTokenAuthAPI (header token)Defined in apps/builder/src/orpc.ts:
authorizedAPI: base → error mapping → authMiddleware (session/cookie auth)workspaceTokenAuthAPI: base → error mapping → workspaceTokenAuthMidddleware (Authorization: Bearer header)Workspace-scoped procedures add workspaceAuthorizedMidddleware per-procedure.
import { authorizedAPI } from "@/orpc"
import { workspaceAuthorizedMidddleware } from "@/middlewares/auth"
import { z } from "zod"
import { zodBigintAsString } from "@chatbotx.io/database/schema"
export const myFeatureAuthenticatedAPI = {
listMyFeatureAPI: authorizedAPI
.route({
method: "GET",
path: "/workspaces/{workspaceId}/my-feature",
summary: "List my feature items",
tags: ["MyFeature"],
})
.input(
z.object({
workspaceId: zodBigintAsString(),
perPage: z.coerce.number().optional(),
cursor: z.string().optional(),
}),
)
.use(workspaceAuthorizedMidddleware, (input) => input.workspaceId)
.output(myFeatureListResponse)
.handler(async ({ input, context }) => {
return await listMyFeature(input)
}),
createMyFeatureAPI: authorizedAPI
.route({
method: "POST",
path: "/workspaces/{workspaceId}/my-feature",
summary: "Create a new item",
tags: ["MyFeature"],
})
.input(createMyFeatureRequest)
.use(workspaceAuthorizedMidddleware, (input) => input.workspaceId)
.handler(async ({ input }) => {
return await createMyFeature(input)
}),
}
authorizedAPI
.route({ method, path, summary, tags }) → OpenAPI metadata
.input(zodSchema) → request validation
.use(middleware, mapperFn) → per-procedure middleware (optional)
.output(zodSchema) → response validation (optional)
.handler(async ({ input, context }) => {})→ business logic
Each feature has api/ directory with optional split:
features/my-feature/
api/
index.ts → merges authenticated + workspace-token APIs
authenticated.ts → session-based procedures
workspace-token.ts → token-based procedures (for public API)
import { myFeatureAuthenticatedAPI } from "./authenticated"
import myFeatureWorkspaceTokenAPIs from "./workspace-token"
export const myFeatureAPI = {
...myFeatureWorkspaceTokenAPIs,
...myFeatureAuthenticatedAPI,
}
import { workspaceTokenAuthAPI } from "@/orpc"
const workspaceTokenAPIs = {
findMyFeaturePublicAPI: workspaceTokenAuthAPI
.route({
method: "GET",
path: "/v1/my-feature/{id}",
summary: "Get item by ID",
tags: ["MyFeature"],
})
.input(z.object({ id: zodBigintAsString() }))
.output(publicMyFeatureResponse)
.handler(async ({ context, input }) => {
// context.workspace is available from token auth
return await findMyFeature({
id: input.id,
workspaceId: context.workspace.id,
})
}),
}
export default workspaceTokenAPIs
Add to apps/builder/src/routers/index.ts:
import { myFeatureAPI } from "@/features/my-feature/api"
export const router = {
// ...existing routes
myFeatureAPI,
}
For public API (workspace-token), also add to apps/builder/src/routers/public.ts.
Schemas live in features/<feature>/schema/:
// schema/query.ts — list/filter request
export const listMyFeatureRequest = z.object({
workspaceId: zodBigintAsString(),
perPage: z.coerce.number().optional(),
cursor: z.string().optional(),
keyword: z.string().optional(),
})
// schema/resource.ts — response shapes
export const myFeatureResponse = z.object({
id: z.string(),
name: z.string(),
createdAt: z.date(),
})
// Reuse workspace ID schema
import { withWorkspaceIdSchema } from "@/features/workspaces/schema/resource"
// .input(mySchema.and(withWorkspaceIdSchema))
import { client } from "@/lib/orpc/orpc"
const data = await client.myFeatureAPI.listMyFeatureAPI({ workspaceId })
// Automatically uses createRouterClient with server headers
const data = await client.myFeatureAPI.listMyFeatureAPI({ workspaceId })
Throw ChatbotXException or ModelNotfoundException — they are auto-mapped to oRPC errors in the global onError interceptor:
import { ChatbotXException, notFoundException } from "@chatbotx.io/sdk"
throw notFoundException("Item not found")
throw new ChatbotXException("Custom error", "BAD_REQUEST", 400)
Import the logger from the nearest lib/log or lib/logger module. Never use console in handlers.
import { logger } from "../lib/log"
.handler(async ({ input }) => {
try {
return await doWork(input)
} catch (error) {
logger.error({ err: error, ...input }, "[myFeature] handler failed")
throw error
}
})
Key rule: always use err: error (not error: error) so pino serializes the stack trace.
Create and modify integration channels (messenger, whatsapp, zalo, webchat, etc.) for the chatbot platform. Use when adding a new channel integration, modifying webhook handlers, working with message send/receive, or connecting external platforms.
Scaffold new features following project conventions for the builder app. Use when creating a new feature, page, component, server action, query, or adding a new section to the web application.
Manage turborepo monorepo development workflow including dev servers, builds, linting, and package management. Use when running dev, build, lint, deploy, or managing workspace packages in this pnpm + turbo monorepo.
Create and manage background workers, BullMQ queues, Kafka consumers, and scheduled jobs. Use when adding new workers, creating queues, defining job types, building scheduled tasks, or working with async processing.
Work with Drizzle ORM database schema, migrations, relations, and queries in PostgreSQL. Use when creating tables, modifying schema, writing migrations, defining relations, or querying the database.