Complete API documentation for @gnana997/node-jsonrpc - a transport-agnostic JSON-RPC 2.0 implementation for TypeScript and Node.js.
The JSONRPCClient class provides a full-featured JSON-RPC 2.0 client with support for requests, notifications, batches, middleware, and more.
new JSONRPCClient(config: JSONRPCClientConfig)| Property | Type | Required | Default | Description |
|---|---|---|---|---|
transport |
Transport |
Yes | - | Transport implementation for sending/receiving messages |
requestTimeout |
number |
No | 30000 |
Default request timeout in milliseconds |
debug |
boolean |
No | false |
Enable debug logging |
middleware |
Middleware[] |
No | [] |
Middleware to apply to all requests/responses |
Example:
import { JSONRPCClient } from '@gnana997/node-jsonrpc';
import { MyTransport } from './transports';
const client = new JSONRPCClient({
transport: new MyTransport(),
requestTimeout: 5000,
debug: true,
});Establishes connection to the server via the transport.
async connect(): Promise<void>Example:
await client.connect();
console.log('Connected to server');Throws:
- Error if already connected
- Transport-specific connection errors
Disconnects from the server and cleans up pending requests.
async disconnect(): Promise<void>Example:
await client.disconnect();
console.log('Disconnected from server');Behavior:
- All pending requests are rejected with "Client disconnected" error
- Pending request count is reset to 0
- Emits
disconnectedevent
Sends a JSON-RPC request and waits for the response.
async request<TResult = any>(
method: string,
params?: any,
options?: RequestOptions
): Promise<TResult>Parameters:
| Parameter | Type | Description |
|---|---|---|
method |
string |
The method name to call |
params |
any |
Method parameters (optional) |
options |
RequestOptions |
Request-specific options (optional) |
RequestOptions:
| Property | Type | Description |
|---|---|---|
timeout |
number |
Request timeout in milliseconds (overrides default) |
signal |
AbortSignal |
AbortSignal for request cancellation |
Example:
// Simple request
const result = await client.request('add', { a: 5, b: 3 });
console.log(result); // 8
// With timeout
const result = await client.request('slowMethod', {}, { timeout: 10000 });
// With abort signal
const controller = new AbortController();
const promise = client.request('method', {}, { signal: controller.signal });
// Cancel after 1 second
setTimeout(() => controller.abort(), 1000);
try {
await promise;
} catch (error) {
console.log(error.message); // "Request aborted"
}Throws:
JSONRPCErrorif the server returns an errorErrorif timeout occursErrorif AbortSignal is abortedErrorif client is not connected
Returns:
- Promise resolving to the method result
Sends a JSON-RPC notification (no response expected).
notify(method: string, params?: any): voidParameters:
| Parameter | Type | Description |
|---|---|---|
method |
string |
The notification method name |
params |
any |
Notification parameters (optional) |
Example:
// Send notification
client.notify('statusUpdate', { status: 'processing', progress: 50 });Throws:
Errorif client is not connected
Creates a new batch request builder.
batch(): BatchRequestExample:
const batch = client.batch();
batch.add('add', { a: 1, b: 2 });
batch.add('subtract', { minuend: 10, subtrahend: 5 });
const results = await batch.execute();
console.log(results); // [3, 5]Returns:
BatchRequestinstance for building and executing batch requests
See BatchRequest for detailed batch API.
Adds middleware to the client.
use(middleware: Middleware): voidExample:
import { LoggingMiddleware } from '@gnana997/node-jsonrpc';
client.use(new LoggingMiddleware(console));See Middleware for available middleware and custom middleware creation.
Checks if the client is currently connected.
isConnected(): booleanExample:
if (client.isConnected()) {
await client.request('method', {});
} else {
await client.connect();
}Gets the number of requests waiting for responses.
getPendingRequestCount(): numberExample:
console.log(`Pending requests: ${client.getPendingRequestCount()}`);The JSONRPCClient extends EventEmitter and emits the following events:
Emitted when the client connects to the server.
client.on('connected', () => {
console.log('Client connected');
});Emitted when the client disconnects from the server.
client.on('disconnected', () => {
console.log('Client disconnected');
});Emitted when a transport or protocol error occurs.
client.on('error', (error: Error) => {
console.error('Client error:', error);
});Note: This event is for transport-level and protocol errors, not method errors (which are returned as rejected promises from request()).
Emitted when a notification is received from the server.
client.on('notification', (method: string, params: any) => {
console.log(`Notification: ${method}`, params);
});Example:
client.on('notification', (method, params) => {
if (method === 'progressUpdate') {
console.log(`Progress: ${params.percentage}%`);
}
});The JSONRPCServer class provides a full-featured JSON-RPC 2.0 server with method registration, multi-client support, broadcasting, and middleware.
new JSONRPCServer(config: JSONRPCServerConfig)| Property | Type | Required | Default | Description |
|---|---|---|---|---|
transportServer |
TransportServer |
Yes | - | Transport server for accepting connections |
debug |
boolean |
No | false |
Enable debug logging |
middleware |
Middleware[] |
No | [] |
Middleware to apply to all requests/responses |
onConnection |
(transport: Transport) => void |
No | - | Called when a client connects |
onDisconnection |
(transport: Transport) => void |
No | - | Called when a client disconnects |
Example:
import { JSONRPCServer } from '@gnana997/node-jsonrpc';
import { MyTransportServer } from './transports';
const server = new JSONRPCServer({
transportServer: new MyTransportServer(),
debug: true,
onConnection: (transport) => {
console.log('Client connected');
},
onDisconnection: (transport) => {
console.log('Client disconnected');
},
});Starts the server and begins accepting connections.
async listen(): Promise<void>Example:
await server.listen();
console.log('Server listening for connections');Stops the server and optionally closes all client connections.
async close(options?: { closeConnections?: boolean }): Promise<void>Parameters:
| Property | Type | Default | Description |
|---|---|---|---|
closeConnections |
boolean |
false |
Whether to disconnect all clients |
Example:
// Stop accepting new connections, keep existing ones
await server.close();
// Stop and disconnect all clients
await server.close({ closeConnections: true });Registers a method handler.
registerMethod(name: string, handler: Handler): voidParameters:
| Parameter | Type | Description |
|---|---|---|
name |
string |
Method name |
handler |
Handler |
Handler function |
Handler Signature:
type Handler = (params: any, context: RequestContext) => any | Promise<any>;RequestContext:
| Property | Type | Description |
|---|---|---|
method |
string |
The method name being called |
requestId |
string | number |
The request ID |
transport |
Transport |
The client's transport (for sending notifications) |
Example:
// Simple handler
server.registerMethod('add', async (params) => {
return params.a + params.b;
});
// Handler with context
server.registerMethod('processWithProgress', async (params, context) => {
for (let i = 0; i < 10; i++) {
// Send progress notification to this client
context.transport.send(JSON.stringify({
jsonrpc: '2.0',
method: 'progress',
params: { step: i + 1, total: 10 },
}));
await someAsyncWork();
}
return { completed: true };
});
// Handler with error
server.registerMethod('divide', async (params) => {
if (params.b === 0) {
throw new Error('Division by zero');
}
return params.a / params.b;
});Removes a method handler.
unregisterMethod(name: string): booleanReturns:
trueif method was found and removedfalseif method was not registered
Example:
const removed = server.unregisterMethod('oldMethod');
console.log(removed); // true or falseChecks if a method is registered.
hasMethod(name: string): booleanExample:
if (server.hasMethod('add')) {
console.log('Add method is registered');
}Returns all registered method names.
listMethods(): string[]Example:
const methods = server.listMethods();
console.log('Available methods:', methods);
// ['add', 'subtract', 'multiply', 'divide']Sends a notification to a specific client.
notify(transport: Transport, method: string, params?: any): voidParameters:
| Parameter | Type | Description |
|---|---|---|
transport |
Transport |
The client's transport |
method |
string |
Notification method name |
params |
any |
Notification parameters (optional) |
Example:
// In a handler, send notification to the calling client
server.registerMethod('startWork', async (params, context) => {
server.notify(context.transport, 'workStarted', { jobId: params.jobId });
// Do work...
server.notify(context.transport, 'workCompleted', { jobId: params.jobId });
return { success: true };
});Sends a notification to all connected clients.
broadcast(method: string, params?: any): numberParameters:
| Parameter | Type | Description |
|---|---|---|
method |
string |
Notification method name |
params |
any |
Notification parameters (optional) |
Returns:
- Number of clients the notification was sent to
Example:
const count = server.broadcast('serverStatus', { status: 'healthy', uptime: 3600 });
console.log(`Broadcast sent to ${count} clients`);Gets the number of currently connected clients.
getConnectionCount(): numberExample:
console.log(`Connected clients: ${server.getConnectionCount()}`);The JSONRPCServer extends EventEmitter and emits the following events:
Emitted when a notification is received from a client.
server.on('notification', (method: string, params: any, transport: Transport) => {
console.log(`Received notification: ${method}`, params);
});Emitted when a transport or protocol error occurs.
server.on('error', (error: Error) => {
console.error('Server error:', error);
});The BatchRequest class allows building and executing multiple JSON-RPC requests in a single batch.
Adds a request to the batch.
add(method: string, params?: any): thisReturns:
thisfor method chaining
Example:
const batch = client.batch();
batch
.add('add', { a: 1, b: 2 })
.add('subtract', { minuend: 10, subtrahend: 5 })
.add('multiply', { a: 3, b: 4 });Executes the batch request.
async execute<TResult = any>(options?: BatchOptions): Promise<TResult[]>BatchOptions:
| Property | Type | Default | Description |
|---|---|---|---|
mode |
'parallel' | 'sequential' |
'parallel' |
Execution mode |
timeout |
number |
30000 |
Timeout for the entire batch |
signal |
AbortSignal |
- | AbortSignal for batch cancellation |
Execution Modes:
parallel(default): All requests execute concurrently. Results may be returned in any order. If any request fails, the error is thrown when mapping results.sequential: Requests execute one-by-one in order. Stops at the first error.
Example:
// Parallel execution (default)
const batch1 = client.batch();
batch1.add('method1', {});
batch1.add('method2', {});
const results1 = await batch1.execute();
// Sequential execution
const batch2 = client.batch();
batch2.add('method1', {});
batch2.add('method2', {});
const results2 = await batch2.execute({ mode: 'sequential' });
// With timeout
const batch3 = client.batch();
batch3.add('slowMethod', {});
const results3 = await batch3.execute({ timeout: 10000 });
// With abort signal
const controller = new AbortController();
const batch4 = client.batch();
batch4.add('method', {});
const promise = batch4.execute({ signal: controller.signal });
setTimeout(() => controller.abort(), 1000);Throws:
Errorif batch is empty ("Cannot execute empty batch")JSONRPCErrorif any request failsErrorif timeout occursErrorif AbortSignal is aborted
Returns:
- Promise resolving to an array of results
Gets the number of requests in the batch.
get length(): numberExample:
const batch = client.batch();
batch.add('method1', {});
batch.add('method2', {});
console.log(batch.length); // 2Middleware allows intercepting and modifying requests, responses, errors, and notifications.
interface Middleware {
onRequest?(request: JSONRPCRequest): JSONRPCRequest | Promise<JSONRPCRequest>;
onResponse?(response: JSONRPCResponse): JSONRPCResponse | Promise<JSONRPCResponse>;
onError?(error: JSONRPCError): JSONRPCError | Promise<JSONRPCError>;
onNotification?(notification: JSONRPCNotification): void | Promise<void>;
onBatchRequest?(request: JSONRPCBatchRequest): JSONRPCBatchRequest | Promise<JSONRPCBatchRequest>;
onBatchResponse?(response: JSONRPCBatchResponse): JSONRPCBatchResponse | Promise<JSONRPCBatchResponse>;
}const loggingMiddleware: Middleware = {
onRequest: (req) => {
console.log('→ Request:', req.method, req.params);
return req;
},
onResponse: (res) => {
console.log('← Response:', res.result);
return res;
},
onError: (err) => {
console.error('✗ Error:', err.message);
return err;
},
};
client.use(loggingMiddleware);Logs all JSON-RPC activity.
import { LoggingMiddleware } from '@gnana997/node-jsonrpc';
const logger = {
info: (msg, data) => console.log(msg, data),
error: (msg, data) => console.error(msg, data),
warn: (msg, data) => console.warn(msg, data),
debug: (msg, data) => console.debug(msg, data),
};
client.use(new LoggingMiddleware(logger));Tracks request metrics (timing, counts, errors).
import { MetricsMiddleware } from '@gnana997/node-jsonrpc';
const metrics = new MetricsMiddleware({
onMetric: (metric) => {
if (metric.type === 'request') {
console.log(`Request: ${metric.method}`);
} else if (metric.type === 'response') {
console.log(`Response time: ${metric.duration}ms`);
}
},
});
client.use(metrics);Transforms requests and responses.
import { TransformMiddleware } from '@gnana997/node-jsonrpc';
const transformer = new TransformMiddleware({
transformRequest: (req) => ({
...req,
params: { ...req.params, timestamp: Date.now() },
}),
transformResponse: (res) => ({
...res,
result: { ...res.result, processed: true },
}),
});
client.use(transformer);Validates requests and responses.
import { ValidationMiddleware } from '@gnana997/node-jsonrpc';
const validator = new ValidationMiddleware({
validateRequest: (req) => {
if (!req.params || typeof req.params !== 'object') {
throw new Error('Invalid params');
}
},
validateResponse: (res) => {
if (res.result === undefined) {
throw new Error('Missing result');
}
},
});
client.use(validator);The JSONRPCError class represents JSON-RPC 2.0 errors.
class JSONRPCError extends Error {
code: number;
data?: any;
constructor(message: string, code: number, data?: any);
toJSON(): { code: number; message: string; data?: any };
// Static factory methods
static parseError(): JSONRPCError;
static invalidRequest(): JSONRPCError;
static methodNotFound(): JSONRPCError;
static invalidParams(): JSONRPCError;
static internalError(message?: string): JSONRPCError;
}| Code | Constant | Message | Description |
|---|---|---|---|
-32700 |
Parse error | Invalid JSON | Invalid JSON was received |
-32600 |
Invalid Request | Invalid Request | The JSON sent is not a valid Request object |
-32601 |
Method not found | Method not found | The method does not exist |
-32602 |
Invalid params | Invalid params | Invalid method parameter(s) |
-32603 |
Internal error | Internal error | Internal JSON-RPC error |
Example:
try {
await client.request('divide', { a: 10, b: 0 });
} catch (error) {
if (error instanceof JSONRPCError) {
console.error(`Error ${error.code}: ${error.message}`);
if (error.data) {
console.error('Additional data:', error.data);
}
}
}// Request
interface JSONRPCRequest {
jsonrpc: '2.0';
method: string;
params?: any;
id: string | number;
}
// Response (success)
interface JSONRPCResponse {
jsonrpc: '2.0';
result: any;
id: string | number;
}
// Response (error)
interface JSONRPCErrorResponse {
jsonrpc: '2.0';
error: {
code: number;
message: string;
data?: any;
};
id: string | number | null;
}
// Notification
interface JSONRPCNotification {
jsonrpc: '2.0';
method: string;
params?: any;
}
// Batch
type JSONRPCBatch = Array<JSONRPCRequest | JSONRPCNotification>;
type JSONRPCBatchRequest = JSONRPCRequest[];
type JSONRPCBatchResponse = Array<JSONRPCResponse | JSONRPCErrorResponse>;interface Transport {
send(message: string): void;
on(event: 'message', handler: (message: string) => void): void;
on(event: 'error', handler: (error: Error) => void): void;
on(event: 'disconnect', handler: () => void): void;
connect(): Promise<void>;
disconnect(): Promise<void>;
isConnected(): boolean;
}
interface TransportServer {
on(event: 'connection', handler: (transport: Transport) => void): void;
listen(): Promise<void>;
close(): Promise<void>;
}See TRANSPORT.md for implementing custom transports.
Utility functions for checking JSON-RPC message types:
import {
isRequest,
isResponse,
isErrorResponse,
isNotification,
isBatch,
isBatchRequest,
isBatchResponse,
isJSONRPCMessage,
} from '@gnana997/node-jsonrpc';
if (isRequest(message)) {
// message is JSONRPCRequest
}
if (isBatch(message)) {
// message is JSONRPCBatch (requests and/or notifications)
}The default ID generator creates monotonically increasing numeric IDs:
import { defaultIDGenerator, IDGenerator } from '@gnana997/node-jsonrpc';
// Custom ID generator
class UUIDGenerator implements IDGenerator {
next(): string {
return crypto.randomUUID();
}
}
const client = new JSONRPCClient({
transport,
// Custom ID generator would need to be passed if the client accepted it
});import { createLogger, noopLogger, type Logger } from '@gnana997/node-jsonrpc';
// Create custom logger
const logger = createLogger({
level: 'debug',
prefix: '[MyApp]',
output: console,
});
// Use noop logger (no output)
const silent = noopLogger;import { JSONRPCClient, JSONRPCServer } from '@gnana997/node-jsonrpc';
import { MyTransport, MyTransportServer } from './transports';
// Server
const server = new JSONRPCServer({
transportServer: new MyTransportServer(),
});
server.registerMethod('add', async (params) => {
return params.a + params.b;
});
await server.listen();
// Client
const client = new JSONRPCClient({
transport: new MyTransport(),
});
await client.connect();
const result = await client.request('add', { a: 5, b: 3 });
console.log(result); // 8
await client.disconnect();
await server.close({ closeConnections: true });import {
JSONRPCClient,
LoggingMiddleware,
MetricsMiddleware,
} from '@gnana997/node-jsonrpc';
const client = new JSONRPCClient({
transport: new MyTransport(),
middleware: [
new LoggingMiddleware(console),
new MetricsMiddleware({
onMetric: (m) => console.log('Metric:', m),
}),
],
});
await client.connect();
await client.request('method', {});const batch = client.batch();
batch
.add('add', { a: 1, b: 2 })
.add('subtract', { minuend: 10, subtrahend: 5 })
.add('multiply', { a: 3, b: 4 });
// Parallel execution
const results = await batch.execute();
console.log(results); // [3, 5, 12]
// Sequential execution
const results2 = await batch.execute({ mode: 'sequential' });// Server
server.registerMethod('longTask', async (params, context) => {
for (let i = 0; i < 10; i++) {
await delay(100);
context.transport.send(JSON.stringify({
jsonrpc: '2.0',
method: 'progress',
params: { step: i + 1, total: 10 },
}));
}
return { completed: true };
});
// Client
client.on('notification', (method, params) => {
if (method === 'progress') {
console.log(`Progress: ${params.step}/${params.total}`);
}
});
const result = await client.request('longTask', {});
console.log(result); // { completed: true }For more examples, see EXAMPLES.md and the examples/ directory.