Quick Start
Get started with DeesseJS RPC in minutes
Introduction
DeesseJS RPC is a type-safe RPC protocol for Node.js and browsers with full TypeScript inference. Define your API once, call it from anywhere with full type safety.
Core Protocol
Handles queries, mutations, context, and routing logic
Server Adapters
Adapters for Next.js, Hono, and other frameworks
Client Libraries
Type-safe clients for React and Vanilla JS
Cache System
Built-in cache keys and automatic invalidation
Want to learn more?
Read our in-depth What is DeesseJS RPC? introduction.
Terminology
| Term | Description |
|---|---|
| Query | A public read operation, callable from client and server |
| Mutation | A public write operation, callable from client and server |
| Internal Query | A server-only read operation, not exposed via HTTP |
| Internal Mutation | A server-only write operation, not exposed via HTTP |
| Context | The shared state passed to all procedure handlers |
| Router | A collection of procedures grouped under a namespace |
Requirements
- TypeScript 5.0+
- Node.js 20+
Installation
npm install @deessejs/serverCreate Your First API
Define Context
Start by defining your context with the services you need:
// src/api.ts
import { defineContext } from '@deessejs/server'
import { z } from 'zod'
const { t, createAPI } = defineContext({
context: () => ({ db: myDatabase }),
})Define a Query
Create a typed query that fetches data and returns cache keys:
// src/api.ts
import { ok } from '@deessejs/fp'
const getUser = t.query({
args: z.object({ id: z.number() }),
handler: async (ctx, args) => {
const user = await ctx.db.users.find(args.id)
return ok(user, { keys: [['users', { id: args.id }]] })
},
})Define a Mutation
Create a mutation that modifies data and invalidates cache:
// src/api.ts
const createUser = t.mutation({
args: z.object({ name: z.string(), email: z.string().email() }),
handler: async (ctx, args) => {
const user = await ctx.db.users.create(args)
return ok(user, { invalidate: ['users:list'] })
},
})Export the Router
Combine everything into a router and export the type:
// src/api.ts
export const appRouter = t.router({
users: t.router({
get: getUser,
create: createUser,
}),
})
export type AppRouter = typeof appRouterWhy export the type?
The AppRouter type is used by clients to get full TypeScript inference. Import it in your client code.
Setup Server
Choose your framework and install the adapter:
File: app/api/[...route]/route.ts
// app/api/[...route]/route.ts
import { createServerHandler } from '@deessejs/server-next'
import { appRouter } from '@/api'
export const { api: serverApi, startServer } = createServerHandler({
router: appRouter,
})Start the development server with npm run dev.
File: src/index.ts
// src/index.ts
import { createHonoHandler } from '@deessejs/server-hono'
import { Hono } from 'hono'
import { appRouter } from './api'
const app = new Hono()
app.route('/api', createHonoHandler({ router: appRouter }))Start with bun dev or npm run dev.
Security
Only query and mutation are exposed via HTTP. Use internalQuery and internalMutation for server-only operations that should never be callable from clients.
Setup Client
Choose your client library:
File: src/lib/client.ts
// src/lib/client.ts
import { createClient } from '@deessejs/client-react'
import { httpBatchLink } from '@deessejs/client'
import type { AppRouter } from '@/api'
export const client = createClient<AppRouter>({
links: [httpBatchLink({ url: '/api' })],
})Usage in a component:
// src/components/user-list.tsx
import { client } from '@/lib/client'
// Query
const user = await client.users.get.query({ id: 1 })
if (user.ok) {
console.log(user.value) // fully typed
}
// Mutation
const result = await client.users.create.mutation({
name: 'Jane',
email: 'jane@example.com',
})File: src/client.ts
// src/client.ts
import { createClient } from '@deessejs/client'
import type { AppRouter } from './api'
export const client = createClient<AppRouter>({ url: '/api' })Usage:
// src/client.ts
import { client } from './client'
// Query
const user = await client.users.get({ id: 1 })
// Mutation
await client.users.create({ name: 'Jane', email: 'jane@example.com' })