@nestjs-rpc/client
Type-safe RPC client for calling NestJS methods from any frontend. Full TypeScript inference, zero configuration.

Installation • Quick Start • Examples
Complete guides, API reference, and advanced examples
🎯 Why @nestjs-rpc/client?
Stop writing API client wrappers manually. This package gives you:
- 🔒 End-to-End Type Safety - Import your server's manifest type for complete type inference
- 🎯 Zero Boilerplate - No manual API wrappers or type definitions
- 📤 Built-in File Uploads - Upload files with simple
{ file } or { files: [] } options
- ⚡ Proxy-Based API - Call methods like
rpc.user.getUserById('123') - feels like local functions
- 🎨 Framework Agnostic - Works with React, Vue, Angular, Svelte, or vanilla JS
- 🔧 Fully Customizable - Built on Axios with full configuration support
The Traditional Way (Without NestRPC)
const response = await fetch('/api/user/getUserById', {
method: 'POST',
body: JSON.stringify({ id: '123' }),
});
const user = await response.json();
The NestRPC Way
const { data: user } = await rpc.user.getUserById('123');
📦 Installation
npm install @nestjs-rpc/client axios
pnpm add @nestjs-rpc/client axios
yarn add @nestjs-rpc/client axios
Peer Dependencies:
axios - Used as the default HTTP client (you can provide your own)
🚀 Quick Start
1. Import Your Server's Manifest Type
First, make sure your server exports the manifest type:
export const manifest = defineManifest({ });
export type Manifest = typeof manifest;
2. Create the Client
import { RpcClient } from '@nestjs-rpc/client';
import type { Manifest } from '../../server/src/nest-rpc.config';
export const rpcClient = new RpcClient<Manifest>({
baseUrl: 'http://localhost:3000',
apiPrefix: 'nestjs-rpc',
});
export const rpc = rpcClient.routers();
3. Use with Full Type Safety!
const { data: user } = await rpc.user.getUserById('123');
await rpc.user.createUser({
name: 'Jane Doe',
email: 'jane@example.com'
});
const { data: users } = await rpc.user.listUsers();
That's it! You get:
- ✅ Full TypeScript autocomplete
- ✅ Compile-time type checking
- ✅ Runtime type safety
- ✅ Zero boilerplate
📤 File Uploads
NestRPC client has built-in support for file uploads. No FormData handling needed!
Single File Upload
const fileInput = document.querySelector<HTMLInputElement>('input[type="file"]');
const { data } = await rpc.user.uploadAvatar(
{ userId: '123' },
{ file: fileInput.files[0] }
);
Multiple File Upload
const fileInput = document.querySelector<HTMLInputElement>('input[type="file"]');
const { data } = await rpc.files.uploadDocuments(
{ category: 'invoices' },
{ files: Array.from(fileInput.files) }
);
With React Example
function FileUpload() {
const [file, setFile] = useState<File | null>(null);
const handleUpload = async () => {
if (!file) return;
const { data } = await rpc.user.uploadAvatar(
{ userId: '123' },
{ file }
);
console.log('Uploaded:', data);
};
return (
<div>
<input
type="file"
onChange={(e) => setFile(e.target.files?.[0] || null)}
/>
<button onClick={handleUpload}>Upload</button>
</div>
);
}
🔧 Configuration
Basic Configuration
const rpcClient = new RpcClient<Manifest>({
baseUrl: 'http://localhost:3000',
apiPrefix: 'nestjs-rpc',
});
Advanced Configuration
import axios from 'axios';
const axiosInstance = axios.create({
timeout: 10000,
headers: {
'X-Custom-Header': 'value',
},
});
const rpcClient = new RpcClient<Manifest>({
baseUrl: 'http://localhost:3000',
apiPrefix: 'nestjs-rpc',
axiosInstance,
requestOptions: {
headers: {
Authorization: 'Bearer token',
},
},
});
Per-Call Options
Override options for individual calls:
const { data } = await rpc.user.getUserById('123', {
requestOptions: {
headers: {
Authorization: 'Bearer custom-token',
'X-Custom-Header': 'value',
},
},
});
const { data } = await rpc.user.getUserById('123', {
axiosInstance: customAxiosInstance,
});
Dynamic Configuration
Update configuration at runtime:
rpcClient.$setConfigProperty('baseUrl', 'https://api.production.com');
rpcClient.$setConfig({
baseUrl: 'https://api.production.com',
requestOptions: {
headers: {
Authorization: `Bearer ${newToken}`,
},
},
});
const config = rpcClient.$config;
console.log(config.baseUrl);
🎨 Framework Examples
React
import { useState, useEffect } from 'react';
import { rpc } from './rpc-client';
function UserList() {
const [users, setUsers] = useState([]);
useEffect(() => {
rpc.user.listUsers().then(({ data }) => setUsers(data));
}, []);
return (
<ul>
{users.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
Vue 3
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { rpc } from './rpc-client';
const users = ref([]);
onMounted(async () => {
const { data } = await rpc.user.listUsers();
users.value = data;
});
</script>
Angular
import { Component, OnInit } from '@angular/core';
import { rpc } from './rpc-client';
@Component({
selector: 'app-user-list',
template: `
<ul>
<li *ngFor="let user of users">{{ user.name }}</li>
</ul>
`,
})
export class UserListComponent implements OnInit {
users: any[] = [];
async ngOnInit() {
const { data } = await rpc.user.listUsers();
this.users = data;
}
}
Vanilla TypeScript
import { rpc } from './rpc-client';
async function loadUsers() {
const { data: users } = await rpc.user.listUsers();
console.log('Users:', users);
}
loadUsers();
🔒 Type Safety
The client automatically infers types from your server's manifest:
@Route()
async getUserById(id: string): Promise<User> {
return { id, name: 'John', email: 'john@example.com' };
}
const { data } = await rpc.user.getUserById('123');
If your server types change, your client code will show TypeScript errors immediately!
📖 API Reference
RpcClient<Manifest>
Main client class.
const client = new RpcClient<Manifest>(config);
Config:
baseUrl: string - Base URL of your server
apiPrefix?: string - API prefix (default: 'nestjs-rpc')
axiosInstance?: AxiosInstance - Custom Axios instance
requestOptions?: AxiosRequestConfig - Default request options
client.routers()
Get the router proxy for making RPC calls.
const rpc = client.routers();
client.$setConfig(config)
Update the entire configuration.
client.$setConfigProperty(key, value)
Update a single configuration property.
client.$config
Read-only access to current configuration.
🎯 Best Practices
- Export manifest type - Make sure your server exports
export type Manifest = typeof manifest
- Centralize client - Create one client instance and export it
- Use environment variables - Use different base URLs for dev/prod
- Handle errors - Wrap calls in try-catch or use error boundaries
- Type your responses - Use
const { data } destructuring for better types
💡 Examples
Check out the example directory for a complete working example with React.
📚 Need More Help?
Complete guides, API reference, advanced patterns, and troubleshooting
🤝 Contributing
Contributions welcome! See the main README for details.
📄 License
MIT