Creating Custom Plugins
Learn how to create custom plugins for URPC with entities, adapters, and complete implementations.
Overview
Plugins in URPC allow you to extend functionality by defining custom entities and data source adapters. A plugin consists of:
- Entities: Data structures that define your domain models
- Adapters: Implementations that handle data operations for specific sources
- Registration: Plugin configuration that connects entities with adapters
Plugin Structure
A typical plugin has the following structure:
my-plugin/
├── src/
│ ├── entities/
│ │ └── user.ts
│ ├── adapters/
│ │ └── user-adapter.ts
│ ├── plugins/
│ │ └── index.ts
│ └── index.ts
├── package.json
└── tsconfig.json
Step-by-Step Guide
Define Your Entity
Entities define the structure of your data. Use decorators from @unilab/urpc-core
to specify field types:
// src/entities/user.ts
import { Fields } from "@unilab/urpc-core";
export class UserEntity {
@Fields.string()
id = "";
@Fields.string()
name = "";
@Fields.string()
email = "";
@Fields.string()
avatar = "";
}
Create Data Source Adapter
Adapters implement the BaseAdapter
class to handle CRUD operations:
// src/adapters/user-adapter.ts
import {
BaseAdapter,
CreationArgs,
FindManyArgs,
FindOneArgs,
URPCError,
ErrorCodes,
} from "@unilab/urpc-core";
import { UserEntity } from "../entities/user";
// Mock data for demonstration
const userData = [
{
id: "1",
name: "John Doe",
email: "[email protected]",
avatar: "https://example.com/avatar.png",
},
{
id: "2",
name: "Jane Smith",
email: "[email protected]",
avatar: "https://example.com/avatar2.png",
},
];
export class UserAdapter extends BaseAdapter<UserEntity> {
async findMany(args: FindManyArgs<UserEntity>): Promise<UserEntity[]> {
const where = args?.where || {};
if (where.id) {
const idValue = typeof where.id === "object" ? where.id.$eq : where.id;
return userData.filter((user) => user.id === idValue);
}
if (where.name) {
const nameValue = typeof where.name === "object" ? where.name.$eq : where.name;
return userData.filter((user) => user.name === nameValue);
}
if (where.email) {
const emailValue = typeof where.email === "object" ? where.email.$eq : where.email;
return userData.filter((user) => user.email === emailValue);
}
return userData;
}
async findOne(args: FindOneArgs<UserEntity>): Promise<UserEntity | null> {
const { id } = args.where;
if (!id) {
return null;
}
return userData.find((user) => user.id === id) || null;
}
async create(args: CreationArgs<UserEntity>): Promise<UserEntity> {
const { name, email, avatar } = args.data;
if (!name || !email || !avatar) {
throw new URPCError(ErrorCodes.BAD_REQUEST, "Invalid arguments");
}
const newUser = {
id: (userData.length + 1).toString(),
name,
email,
avatar,
};
userData.push(newUser);
return newUser;
}
}
Create the Plugin Definition
Define your plugin by combining entities and adapters:
// src/plugins/index.ts
import { Plugin } from "@unilab/urpc-core";
import { UserEntity } from "../entities/user";
import { UserAdapter } from "../adapters/user-adapter";
export const UserPlugin: Plugin = {
entities: [UserEntity],
adapters: [
{
source: "demo",
entity: "UserEntity",
adapter: new UserAdapter()
}
]
};
Export Plugin Components
Create the main entry point for your plugin:
// src/index.ts
export * from "./entities/user";
export * from "./adapters/user-adapter";
export * from "./plugins";
Using Your Plugin
Server Setup
Register your plugin in a URPC server:
// server.ts
import { URPC } from "@unilab/urpc-hono";
import { UserPlugin } from "./my-plugin";
const app = URPC.init({
plugins: [UserPlugin]
});
export default {
port: 3000,
fetch: app.fetch
};
Client Usage
Use your plugin entities from the client:
// client.ts
import { repo, URPC } from "@unilab/urpc";
import { UserEntity } from "./my-plugin";
URPC.init({
baseUrl: "http://localhost:3000",
timeout: 10000
});
// Create a user
const createUser = async () => {
const user = await repo<UserEntity>({
entity: "user",
source: "demo",
}).create({
data: {
name: "Alice Johnson",
email: "[email protected]",
avatar: "https://example.com/avatar3.png"
}
});
console.log("Created user:", user);
};
// Find users
const getUsers = async () => {
const users = await repo<UserEntity>({
entity: "user",
source: "demo",
}).findMany({
where: { name: "John Doe" }
});
console.log("Users:", users);
};
// Update a user
const updateUser = async () => {
const updated = await repo<UserEntity>({
entity: "user",
source: "demo",
}).update({
where: { id: "1" },
data: { email: "[email protected]" }
});
console.log("Updated user:", updated);
};
createUser();
getUsers();
updateUser();
Package Configuration
Create a proper package.json
for your plugin:
{
"name": "@mycompany/urpc-user-plugin",
"version": "1.0.0",
"description": "User management plugin for URPC",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"scripts": {
"build": "tsc",
"test": "jest",
"dev": "tsc --watch"
},
"dependencies": {
"@unilab/urpc-core": "^1.0.0"
},
"devDependencies": {
"typescript": "^5.0.0",
"jest": "^29.0.0",
"@types/jest": "^29.0.0"
},
"peerDependencies": {
"@unilab/urpc-core": "^1.0.0"
}
}
Next Steps
- Learn about Middleware for adding cross-cutting concerns
- Explore Advanced Integrations for specific frameworks
- Check out the UniWeb3 Plugin as a real-world example
Next.js Integration
URPC provides complete Next.js support, including both App Router and Pages Router routing methods. This guide will show you how to integrate URPC in Next.js projects.
Mastra Plugin
The Mastra Plugin integrates AI-powered natural language processing capabilities into your URPC applications, enabling users to interact with your data through conversational interfaces.