JSON-RPC Basic Client-Server Example
This example demonstrates how to create a simple JSON-RPC 2.0 server using Express.js and a client that communicates with it. You'll learn the fundamental patterns for implementing JSON-RPC in a JavaScript environment.
What You'll Learn
- How to set up a basic JSON-RPC server using Express.js
- How to implement method handlers on the server
- How to create a client that sends JSON-RPC requests
- How to handle responses and errors
- The JSON-RPC 2.0 message structure and protocol flow
1. Server Implementation
First, let's create a simple JSON-RPC server using Express.js. This server will expose methods for basic arithmetic operations: addition, subtraction, multiplication, and division.
Start by installing the required dependencies:
npm install express body-parser
Now, create a file named server.js with the following code:
const express = require('express');
const bodyParser = require('body-parser');
const app = express();
const PORT = 3000;
// Middleware to parse JSON request body
app.use(bodyParser.json());
// Process JSON-RPC requests
app.post('/jsonrpc', (req, res) => {
const request = req.body;
// Validate JSON-RPC request
if (!request.jsonrpc || request.jsonrpc !== '2.0' || !request.method) {
return res.json({
jsonrpc: '2.0',
error: {
code: -32600,
message: 'Invalid Request'
},
id: request.id || null
});
}
// Process method calls
let result;
let error;
try {
switch (request.method) {
case 'add':
if (!Array.isArray(request.params) || request.params.length !== 2) {
throw { code: -32602, message: 'Invalid params' };
}
result = request.params[0] + request.params[1];
break;
case 'subtract':
if (!Array.isArray(request.params) || request.params.length !== 2) {
throw { code: -32602, message: 'Invalid params' };
}
result = request.params[0] - request.params[1];
break;
case 'multiply':
if (!Array.isArray(request.params) || request.params.length !== 2) {
throw { code: -32602, message: 'Invalid params' };
}
result = request.params[0] * request.params[1];
break;
case 'divide':
if (!Array.isArray(request.params) || request.params.length !== 2) {
throw { code: -32602, message: 'Invalid params' };
}
if (request.params[1] === 0) {
throw { code: -32602, message: 'Division by zero' };
}
result = request.params[0] / request.params[1];
break;
default:
throw { code: -32601, message: 'Method not found' };
}
} catch (e) {
error = {
code: e.code || -32603,
message: e.message || 'Internal error'
};
}
// Build the response
const response = {
jsonrpc: '2.0',
id: request.id
};
if (error) {
response.error = error;
} else {
response.result = result;
}
res.json(response);
});
// Start the server
app.listen(PORT, () => {
console.log(`JSON-RPC server running at http://localhost:${PORT}`);
});
In a production environment, you should add proper logging, input validation, and error handling. This example focuses on the JSON-RPC protocol implementation.
Key Components of the Server:
- Endpoint Setup: We create a single POST endpoint
/jsonrpc
that handles all RPC requests. - Request Validation: We verify that the request follows the JSON-RPC 2.0 specification (has
jsonrpc: '2.0'
and amethod
). - Method Dispatch: Based on the
method
field, we route to the appropriate function. - Parameter Validation: We check that the parameters match what's expected for each method.
- Error Handling: We use standard JSON-RPC error codes and formats.
- Response Structure: We ensure every response includes
jsonrpc: '2.0'
and the sameid
as the request.
2. Client Implementation
Now let's create a simple client that can communicate with our JSON-RPC server. Create a file named client.js:
const fetch = require('node-fetch');
class JsonRpcClient {
constructor(url) {
this.url = url;
this.id = 1;
}
async call(method, params) {
const request = {
jsonrpc: '2.0',
method,
params,
id: this.id++
};
const response = await fetch(this.url, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(request)
});
const result = await response.json();
if (result.error) {
throw new Error(`Error ${result.error.code}: ${result.error.message}`);
}
return result.result;
}
}
// Create client instance
const client = new JsonRpcClient('http://localhost:3000/jsonrpc');
// Example usage
async function runExamples() {
try {
// Addition
const sum = await client.call('add', [10, 5]);
console.log('10 + 5 =', sum);
// Subtraction
const difference = await client.call('subtract', [10, 5]);
console.log('10 - 5 =', difference);
// Multiplication
const product = await client.call('multiply', [10, 5]);
console.log('10 * 5 =', product);
// Division
const quotient = await client.call('divide', [10, 5]);
console.log('10 / 5 =', quotient);
// Error case - division by zero
try {
await client.call('divide', [10, 0]);
} catch (e) {
console.log('Error caught:', e.message);
}
// Error case - method not found
try {
await client.call('power', [2, 3]);
} catch (e) {
console.log('Error caught:', e.message);
}
} catch (e) {
console.error('Error:', e);
}
}
// Run the examples
runExamples();
Key Components of the Client:
- JsonRpcClient Class: A simple class that handles creating and sending JSON-RPC requests.
- Request Construction: We create a properly formatted JSON-RPC 2.0 request with all required fields.
- ID Management: The client automatically increments the request ID to match requests with responses.
- Error Handling: We check for error responses and throw them as JavaScript exceptions.
- Method Examples: We demonstrate calling each method and handling various error cases.
3. Running the Example
To run this example, you'll need to:
- Save both files (
server.js
andclient.js
) in the same directory - Install the required packages with
npm install express body-parser node-fetch
- Start the server in one terminal with
node server.js
- Run the client in another terminal with
node client.js
You should see the following output from the client:
10 + 5 = 15 10 - 5 = 5 10 * 5 = 50 10 / 5 = 2 Error caught: Error -32602: Division by zero Error caught: Error -32601: Method not found
4. Key Concepts
JSON-RPC Message Structure
Every JSON-RPC 2.0 request must include:
jsonrpc
: Must be exactly"2.0"
method
: The name of the method to callparams
: An array or object of parameters (optional)id
: A unique identifier for matching requests and responses
Error Handling
JSON-RPC defines standard error codes:
-32600
: Invalid Request-32601
: Method not found-32602
: Invalid params-32603
: Internal error-32000 to -32099
: Server error (reserved)
Best Practices
- Use a single endpoint for all methods to simplify your API
- Implement proper error handling with standard error codes
- Consider using named parameters (object) instead of positional parameters (array) for clarity
- Always include the
id
from the request in your response - Validate all input parameters before processing
- Use HTTPS in production for secure communication
5. Next Steps
Now that you understand the basics of JSON-RPC, you might want to explore: