Quick Start
Get started with DeesseJS Functions in minutes
Get up and running with DeesseJS Functions in under 5 minutes.
Installation
Install the package using your preferred package manager:
# npm
npm install @deessejs/functions
# pnpm
pnpm add @deessejs/functions
# yarn
yarn add @deessejs/functionsPeer Dependency: DeesseJS Functions requires Zod for schema validation.
npm install zodBasic Usage
Let's create your first API with Deesse Functions.
1. Define Context
Context is your single source of truth - define it once, use it everywhere:
import { defineContext, success } from '@deessejs/functions';
import { z } from 'zod';
const { t, createAPI } = defineContext({
userId: "guest",
locale: "en",
});This code does three things:
defineContext()creates your context with the initial values you providetis a builder object you'll use to define queries and mutationscreateAPI()activates your endpoints and makes them available to call
The context values (userId, locale) will be automatically available in all your query and mutation handlers - no need to pass them manually.
2. Create Queries and Mutations
Queries are for reading data, Mutations are for writing data:
// A query to fetch a user
const getUser = t.query({
args: z.object({ id: z.number() }),
handler: async (ctx, args) => {
// Access context with ctx.userId, ctx.locale, etc.
// Return success with data
return success({ id: args.id, name: "Alice" });
},
});
// A mutation to create a user
const createUser = t.mutation({
args: z.object({
name: z.string().min(2),
email: z.string().email(),
}),
handler: async (ctx, args) => {
// Create user in your database
const newUser = { id: 1, ...args };
return success(newUser);
},
});Each endpoint has three parts:
t.query()ort.mutation()- Defines whether this reads or writes dataargs- A Zod schema that validates input automatically before your handler runshandler- Your function wherectx(context) andargs(validated input) are availablesuccess()- Returns a successful result containing your data
3. Create Your API
Combine your endpoints into an API:
const api = createAPI({
users: t.router({
get: getUser,
create: createUser,
}),
});Routers organize related endpoints together. You can nest them deeply:
api.users.get()- Calls the getUser queryapi.users.create()- Calls the createUser mutation
The t.router() function groups endpoints logically, making your API structure cleaner and easier to navigate.
4. Use Your API
Call your endpoints with full type safety:
// Fetch a user
const result = await api.users.get({ id: 1 });
if (result.success) {
console.log(result.value); // { id: 1, name: "Alice" }
} else {
console.error(result.error);
}
// Create a new user
const newUser = await api.users.create({
name: "Bob",
email: "bob@example.com",
});This demonstrates type safety in action:
- Arguments are validated by Zod - passing invalid types like
{ id: "wrong" }will fail at runtime - Return values are fully typed - TypeScript knows exactly what properties
result.valuecontains - Errors are handled explicitly with the
result.successcheck - No more
anytypes or manual type casting needed!
Complete Example
Here's a complete working example:
import { defineContext, success } from '@deessejs/functions';
import { z } from 'zod';
// 1. Define context with your dependencies
const { t, createAPI } = defineContext({
database: myDatabase,
userId: "user-123",
});
// 2. Define queries (read operations)
const getUser = t.query({
args: z.object({ id: z.number() }),
handler: async (ctx, args) => {
const user = await ctx.database.users.find(args.id);
return success(user);
},
});
const listPosts = t.query({
args: z.object({
limit: z.number().min(1).max(100).default(20),
}),
handler: async (ctx, args) => {
const posts = await ctx.database.posts.findAll({ limit: args.limit });
return success(posts);
},
});
// 3. Define mutations (write operations)
const createPost = t.mutation({
args: z.object({
title: z.string().min(1).max(200),
content: z.string().min(10),
}),
handler: async (ctx, args) => {
const post = await ctx.database.posts.create({
...args,
authorId: ctx.userId,
});
return success(post);
},
});
// 4. Create and export the API
export const api = createAPI({
users: t.router({
get: getUser,
}),
posts: t.router({
list: listPosts,
create: createPost,
}),
});Next Steps
What is Deesse Functions?
Learn about the library's philosophy and features
Queries & Mutations
Deep dive into endpoints, caching, and invalidation
Context System
Managing shared state and dependencies
Result Types
Handling success and failure cases
Type Safety in Action
Deesse Functions provides full end-to-end type safety:
// Arguments are type-checked
const user = await api.users.get({ id: 1 });
const user = await api.users.get({ id: "wrong" }); // ❌ Type error!
// Return types are inferred
user.value.name; // ✅ TypeScript knows this exists
user.value.wrongField; // ❌ Type error!Error Handling
Handle errors gracefully with Result types:
import { success, failure } from '@deessejs/functions';
const getUser = t.query({
args: z.object({ id: z.number() }),
handler: async (ctx, args) => {
const user = await ctx.database.find(args.id);
if (!user) {
return failure(new Error("User not found"));
}
return success(user);
},
});
// Usage
const result = await api.users.get({ id: 999 });
if (!result.success) {
console.error("Failed:", result.error.message);
// Handle error
}
// Access value safely
if (result.success) {
console.log(result.value); // User data
}Validation is Automatic
All arguments are validated automatically using Zod schemas. Invalid inputs return validation errors without running your handler.
TypeScript Configuration
Ensure your tsconfig.json has the following settings for the best experience:
{
"compilerOptions": {
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"target": "ES2020",
"module": "ESNext",
"moduleResolution": "bundler"
}
}