DeesseJS RPC

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.

Want to learn more?

Read our in-depth What is DeesseJS RPC? introduction.

Terminology

TermDescription
QueryA public read operation, callable from client and server
MutationA public write operation, callable from client and server
Internal QueryA server-only read operation, not exposed via HTTP
Internal MutationA server-only write operation, not exposed via HTTP
ContextThe shared state passed to all procedure handlers
RouterA collection of procedures grouped under a namespace

Requirements

  • TypeScript 5.0+
  • Node.js 20+

Installation

npm install @deessejs/server

Create 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 appRouter

Why 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' })

FAQ

Next Steps

On this page