
Research
/Security News
10 npm Typosquatted Packages Deploy Multi-Stage Credential Harvester
Socket researchers found 10 typosquatted npm packages that auto-run on install, show fake CAPTCHAs, fingerprint by IP, and deploy a credential stealer.
@danscan/zod-jsonrpc
Advanced tools
Create JSON-RPC 2.0-compliant clients and servers that use Zod to validate requests and responses.
Create type-safe JSON-RPC API clients and servers in JS/TS using any transport.
| zod-jsonrpc Version | Zod Version | Zod Import | Notes |
|---|---|---|---|
| v3.0.0+ | ^3.25.0 | import { z } from 'zod/v4' | Uses Zod v4 API |
| v2.1.0 | ^3.0.0 | import { z } from 'zod' | Legacy Zod API support |
bun add @danscan/zod-jsonrpc
yarn add @danscan/zod-jsonrpc
npm add @danscan/zod-jsonrpc
A minimal server example:
import { createServer, method } from '@danscan/zod-jsonrpc';
import { z } from 'zod/v4';
// Define and create a server in one step
const server = createServer({
greet: method({
paramsSchema: z.object({ name: z.string() }),
resultSchema: z.string(),
}, ({ name }) => `Hello, ${name}!`),
});
// Handle a request
const response = await server.request({
id: 1,
method: 'greet',
params: { name: 'World' },
jsonrpc: '2.0',
});
console.log(response.result); // "Hello, World!"
import { createServer, method, JSONRPC2Error } from '@danscan/zod-jsonrpc';
import { z } from 'zod/v4';
const server = createServer({
greet: method({
paramsSchema: z.object({ name: z.string() }),
resultSchema: z.string(),
}, ({ name }) => `Hello, ${name}!`),
mustBeOdd: method({
paramsSchema: z.object({ number: z.number() }),
resultSchema: z.boolean(),
}, ({ number }) => {
const isOdd = number % 2 === 1;
// Throw a JSONRPC2Error to return a JSON-RPC 2.0 compliant error response
if (!isOdd) {
throw new JSONRPC2Error.InvalidParams({
message: 'Number must be odd',
data: { number },
});
}
return true;
}),
});
// Single request handling
const result = await server.request({
id: 1,
method: 'greet',
params: { name: 'danscan' },
jsonrpc: '2.0',
});
/*
{
id: 1,
result: 'Hello, danscan!',
jsonrpc: '2.0',
}
*/
// Batch request handling (multiple requests in one call)
const results = await server.request([
{ id: 1, method: 'greet', params: { name: 'danscan' }, jsonrpc: '2.0' },
{ id: 2, method: 'greet', params: { name: 'user' }, jsonrpc: '2.0' },
{ id: 3, method: 'mustBeOdd', params: { number: 4 }, jsonrpc: '2.0' },
]);
/*
[
{ id: 1, result: 'Hello, danscan!', jsonrpc: '2.0' },
{ id: 2, result: 'Hello, user!', jsonrpc: '2.0' },
{ id: 3, error: { code: -32602, message: 'Invalid params: Number must be odd', data: { number: 4 } }, jsonrpc: '2.0' },
]
*/
const jsonRpcServer = createServer({ greet, mustBeOdd });
// Bun
Bun.serve({
fetch: async (req) => {
const jsonRpcRequest = await req.json();
const jsonRpcResponse = await jsonRpcServer.request(jsonRpcRequest);
return Response.json(jsonRpcResponse);
}
});
// Next.js
export async function POST(request: Request) {
const jsonRpcRequest = await request.json();
const jsonRpcResponse = await jsonRpcServer.request(jsonRpcRequest);
return Response.json(jsonRpcResponse);
}
// Express.js
app.post('/jsonrpc', async (req, res) => {
const jsonRpcResponse = await jsonRpcServer.request(req.body);
res.json(jsonRpcResponse);
});
import { createClient, method } from '@danscan/zod-jsonrpc';
import { z } from 'zod/v4';
// Define your transport layer
// HTTP:
const sendRequest = async (request) => {
const response = await fetch('/jsonrpc', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(request),
});
return response.json();
};
// WebSocket:
const sendRequestViaWebSocket = async (request) => {
const ws = new WebSocket('ws://localhost:8080');
ws.onmessage = (event) => {
return JSON.parse(event.data);
};
ws.send(JSON.stringify(request));
};
// Create the client with method definitions
const client = createClient({
greet: method({
paramsSchema: z.object({ name: z.string() }),
resultSchema: z.string(),
}),
}, sendRequest);
// Make method calls
const greeting = await client.greet({ name: 'danscan' });
console.log(greeting); // 'Hello, danscan!'
// Batch method calls with named results
const results = await client.batch((ctx) => ({
dan: ctx.greet({ name: 'Dan' }),
drea: ctx.greet({ name: 'Drea' }),
}));
/*
{
dan: { ok: true, value: 'Hello, Dan!' },
drea: { ok: true, value: 'Hello, Drea!' },
}
*/
Pro tip: If you already have a server defined, create a client from it to ensure consistency:
import { server } from './server';
function sendRequest(request) {
// ...
}
const client = server.createClient(sendRequest);
const result = await client.greet({ name: 'danscan' });
console.log(result); // 'Hello, danscan!'
For larger applications, I recommend separating method definitions from their implementations. This allows you to:
src/
api/
methods/
index.ts # Exports all methods
auth.ts # Auth methods
user.ts # User methods
client.ts # Client definition
server.ts # Server definition
// ... rest of your app
// api/methods/user.ts
import { method } from '@danscan/zod-jsonrpc';
import { z } from 'zod/v4';
/** Get user profile by ID */
export const getUser = method({
paramsSchema: z.object({
userId: z.string().uuid()
}),
resultSchema: z.object({
id: z.string().uuid(),
name: z.string(),
email: z.string().email(),
}),
});
/** Update user profile */
export const updateUser = method({
paramsSchema: z.object({
userId: z.string().uuid(),
updates: z.object({
name: z.string().optional(),
email: z.string().email().optional(),
}),
}),
resultSchema: z.object({
success: z.boolean(),
}),
});
// api/methods/index.ts
export * from './auth';
export * from './user';
// api/server.ts
import { createServer } from '@danscan/zod-jsonrpc';
import * as methods from './methods';
import { getUserFromDb, updateUserInDb } from '../db';
export const server = createServer({
getUser: methods.getUser.implement(async ({ userId }) => {
const user = await getUserFromDb(userId);
if (!user) throw new Error('User not found');
return user;
}),
updateUser: methods.updateUser.implement(async ({ userId, updates }) => {
await updateUserInDb(userId, updates);
return { success: true };
}),
});
// api/client.ts
import { createClient } from '@danscan/zod-jsonrpc';
import * as methods from './methods';
const sendRequest = async (request) => {
const response = await fetch('/api/jsonrpc', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(request),
});
return response.json();
};
export const apiClient = createClient({
getUser: methods.getUser,
updateUser: methods.updateUser,
}, sendRequest);
FAQs
Create JSON-RPC 2.0-compliant clients and servers that use Zod to validate requests and responses.
We found that @danscan/zod-jsonrpc demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 0 open source maintainers collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Research
/Security News
Socket researchers found 10 typosquatted npm packages that auto-run on install, show fake CAPTCHAs, fingerprint by IP, and deploy a credential stealer.

Product
Socket Firewall Enterprise is now available with flexible deployment, configurable policies, and expanded language support.

Security News
Open source dashboard CNAPulse tracks CVE Numbering Authorities’ publishing activity, highlighting trends and transparency across the CVE ecosystem.