React Native HTTP Server with Nitro Modules
npm install react-native-nitro-http-serverA high-performance React Native HTTP server library, implemented in Rust, supporting dynamic request handling and static file serving.

![platform]()
中文文档
- 🚀 High Performance: Built on Rust's Actix-web framework, delivering exceptional performance.
- 📱 Cross-Platform: Supports iOS and Android.
- 🔄 Asynchronous: Uses Nitro Modules to provide native async APIs.
- 📁 Static File Serving: Built-in static file server support.
- 📂 Directory Listing: Automatically generate directory listing pages.
- 🎯 Easy to Use: TypeScript-friendly API design.
- ⚡ Zero Copy: Direct FFI calls to Rust code.
- 🔌 Plugin System: Support for WebDAV, Zip mounting, and extensible plugins.
- 🌊 Streaming APIs: Support for streaming request/response bodies.
- 📤 File Upload Plugin: Support for handling multipart/form-data file uploads efficiently (save to disk).
- 💾 Buffer Upload Plugin: Handle file uploads in memory with direct ArrayBuffer access.
- 🔀 URL Rewrite Plugin: Support pattern-based URL rewriting using regular expressions.
- 🔌 WebSocket Plugin: Real-time bidirectional communication with full handshake info access.
- 🔄 Node.js Compatible: Compatible with Node.js http module API.
``bash`
npm install react-native-nitro-http-serveror
yarn add react-native-nitro-http-server
Run pod install:
`bash`
cd ios && pod install
No extra configuration needed, autolinking is supported.
`typescript
import { HttpServer } from 'react-native-nitro-http-server';
const server = new HttpServer();
// Start server
await server.start(8080, async (request) => {
console.log(Received request: ${request.method} ${request.path});
return {
statusCode: 200,
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
message: 'Hello from React Native!',
path: request.path,
}),
};
});
console.log('Server running at http://localhost:8080');
// Stop server
// await server.stop();
`
`typescript
import { HttpServer } from 'react-native-nitro-http-server';
const server = new HttpServer();
await server.start(8080, async (request) => {
// Return a binary image
const imageBuffer = new ArrayBuffer(1024); // Your binary data
return {
statusCode: 200,
headers: {
'Content-Type': 'image/png',
},
body: imageBuffer, // direct ArrayBuffer support
};
});
`
`typescript
import { StaticServer } from 'react-native-nitro-http-server';
import RNFS from 'react-native-fs';
const server = new StaticServer();
// Start static file server
const staticDir = RNFS.DocumentDirectoryPath + '/www';
await server.start(8080, staticDir);
console.log(Static file server running at http://localhost:8080);Serving directory: ${staticDir}
console.log();
// Stop static server
// await server.stop();
`
Supports both static file serving and dynamic API handling. It prioritizes serving static files; if the file does not exist, it invokes the callback function.
`typescript
import { AppServer } from 'react-native-nitro-http-server';
import RNFS from 'react-native-fs';
const server = new AppServer();
const staticDir = RNFS.DocumentDirectoryPath + '/www';
// Start app server (hybrid mode)
await server.start(8080, staticDir, async (request) => {
// This callback is executed when the static file is not found
return {
statusCode: 200,
body: Dynamic response for ${request.path},`
};
});
Supports advanced features like WebDAV and Zip file mounting through plugin configuration.
`typescript
import { createConfigServer } from 'react-native-nitro-http-server';
import RNFS from 'react-native-fs';
const staticDir = RNFS.DocumentDirectoryPath + '/www';
// Configure plugins
const config = {
root_dir: staticDir, // Static file root (Optional)
verbose: 'info', // Log level: 'off' | 'error' | 'warn' | 'info' | 'debug' (default: 'off')
mounts: [
{
type: 'webdav',
path: '/webdav',
root: RNFS.DocumentDirectoryPath + '/webdav'
},
{
type: 'zip',
path: '/archive',
zip_file: RNFS.DocumentDirectoryPath + '/content.zip'
},
{
type: 'static',
path: '/static',
root: staticDir,
dir_list: {
enabled: true, // Enable directory listing
show_hidden: false
}
},
{
type: 'upload',
path: '/upload',
temp_dir: RNFS.CachesDirectoryPath + '/uploads'
},
{
type: 'buffer_upload',
path: '/buffer-upload'
},
{
type: 'rewrite',
rules: [
{ pattern: '^/old/(.*)', replacement: '/static/$1' },
{ pattern: '^/api/v1/(.*)', replacement: '/api/v2/$1' }
]
},
{
type: 'websocket',
path: '/ws'
}
],
mime_types: {
"myext": "application/x-custom-type" // Custom MIME type
}
};
// Start server with plugin configuration
const server = await createConfigServer(8080, async (request) => {
// Handle dynamic requests
return {
statusCode: 200,
body: API response for ${request.path},
};
}, config);
// Now you can:
// - Access WebDAV at http://localhost:8080/webdav
// - Access zip content at http://localhost:8080/archive
// - Browse directories if index file is missing
// - Static files from staticDir
// - Dynamic API responses
// - WebSocket at ws://localhost:8080/ws
`
Provides real-time bidirectional communication with full access to handshake information.
`typescript
import { ConfigServer } from 'react-native-nitro-http-server';
const server = new ConfigServer();
// Register WebSocket handler (before starting the server)
server.onWebSocket('/ws', (ws, request) => {
// Access handshake info
console.log('Path:', request.path);
console.log('Query:', request.query); // e.g., "token=abc&user=123"
console.log('Headers:', request.headers); // Full HTTP handshake headers
// Handle events
ws.onmessage = (e) => {
console.log('Received:', e.data);
ws.send('Echo: ' + e.data);
};
ws.onclose = (e) => {
console.log('Closed:', e.code, e.reason);
};
});
// Start server with WebSocket mount
await server.start(8080, httpHandler, {
mounts: [{ type: 'websocket', path: '/ws' }]
});
`
`typescript
import { HttpServer } from 'react-native-nitro-http-server';
const server = new HttpServer();
// Mock database
const users = [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' },
];
await server.start(8080, async (request) => {
const { method, path } = request;
// GET /api/users - Get all users
if (method === 'GET' && path === '/api/users') {
return {
statusCode: 200,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(users),
};
}
// GET /api/users/:id - Get a single user
const userMatch = path.match(/^\/api\/users\/(\d+)$/);
if (method === 'GET' && userMatch) {
const userId = parseInt(userMatch[1]);
const user = users.find(u => u.id === userId);
if (user) {
return {
statusCode: 200,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(user),
};
} else {
return {
statusCode: 404,
body: JSON.stringify({ error: 'User not found' }),
};
}
}
// POST /api/users - Create a new user
if (method === 'POST' && path === '/api/users') {
const newUser = JSON.parse(request.body || '{}');
newUser.id = users.length + 1;
users.push(newUser);
return {
statusCode: 201,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(newUser),
};
}
// 404 - Route not found
return {
statusCode: 404,
body: JSON.stringify({ error: 'Route not found' }),
};
});
`
Provides an interface compatible with Node.js http module, facilitating migration of existing code or using adapters for frameworks like Express/Koa.
`typescript
import { createServer } from 'react-native-nitro-http-server';
const server = createServer((req, res) => {
console.log(req.method, req.url);
res.statusCode = 200;
res.setHeader('Content-Type', 'text/plain');
res.end('Hello from Node.js compatible API!');
});
server.listen(8080, () => {
console.log('Server listening on port 8080');
});
`
The basic HTTP server class for handling dynamic requests.
#### start(port: number, handler: RequestHandler): Promise
Starts the HTTP server.
Parameters:
- port: Port number (1024-65535)handler
- : Request handler function, receives HttpRequest and returns HttpResponse
Returns: true if started successfully.
Example:
`typescript`
const success = await server.start(8080, async (request) => {
return {
statusCode: 200,
body: 'Hello World',
};
});
#### stop(): Promise
Stops the HTTP server.
Example:
`typescript`
await server.stop();
The static file server class.
#### start(port: number, rootDir: string, host?: string): Promise
Starts the static file server.
Parameters:
- port: Port numberrootDir
- : Absolute path to the static file root directoryhost
- : (Optional) IP address to bind to, defaults to 127.0.0.1
Returns: true if started successfully.
Example:
`typescript
import { StaticServer } from 'react-native-nitro-http-server';
import RNFS from 'react-native-fs';
const server = new StaticServer();
const success = await server.start(
8080,
RNFS.DocumentDirectoryPath + '/www'
);
`
#### stop(): Promise
Stops the static file server.
#### isRunning(): boolean
Checks if the static server is running.
The app server class (hybrid mode) for both static files and dynamic requests.
#### start(port: number, rootDir: string, handler: RequestHandler, host?: string): Promise
Starts the app server (hybrid mode). The server will first attempt to find the corresponding static file in rootDir. If found and the method is GET, it returns the file content directly. Otherwise, it forwards the request to the handler.
Parameters:
- port: Port numberrootDir
- : Static file root directoryhandler
- : Request handlerhost
- : (Optional) IP address to bind to, defaults to 127.0.0.1
#### stop(): Promise
Stops the app server.
#### isRunning(): boolean
Checks if the app server is running.
#### HttpRequest
`typescript`
interface HttpRequest {
requestId: string; // Unique request ID
method: string; // HTTP Method (GET, POST, PUT, DELETE, etc.)
path: string; // Request path
headers: Record
body?: string; // Request body (optional)
binaryBody?: ArrayBuffer; // Binary request body (used by buffer_upload)
}
#### HttpResponse
`typescript`
interface HttpResponse {
statusCode: number; // HTTP Status Code (200, 404, 500, etc.)
headers?: Record
body?: string | ArrayBuffer; // Response body (string or ArrayBuffer)
}
#### stopAppServer(): Promise
Stops the app server.
Server with plugin configuration support (WebDAV, Zip mounting, etc.).
#### start(port: number, handler: RequestHandler, config: ServerConfig, host?: string): Promise
Starts the config server with plugin configuration.
Parameters:
- port: Port numberhandler
- : Request handlerconfig
- : Plugin configuration object (includes root_dir)host
- : (Optional) IP address to bind to, defaults to 127.0.0.1
Example:
`typescript
const config = {
root_dir: staticDir,
mounts: [
{
type: 'webdav',
path: '/webdav',
root: RNFS.DocumentDirectoryPath + '/webdav'
},
{
type: 'zip',
path: '/archive',
zip_file: RNFS.DocumentDirectoryPath + '/content.zip'
}
]
};
const server = new ConfigServer();
await server.start(8080, handler, config, '0.0.0.0');
`
#### stop(): Promise
Stops the config server.
#### isRunning(): boolean
Checks if the config server is running.
#### createHttpServer(port: number, handler: RequestHandler, host?: string): Promise
Creates and starts a basic HTTP server.
#### createStaticServer(port: number, rootDir: string, host?: string): Promise
Creates and starts a static file server.
#### createAppServer(port: number, rootDir: string, handler: RequestHandler, host?: string): Promise
Creates and starts an app server (hybrid mode).
#### createConfigServer(port: number, handler: RequestHandler, config: ServerConfig, host?: string): Promise
Creates and starts a config server with plugin configuration.
#### HttpRequest
`typescript`
interface HttpRequest {
requestId: string; // Unique request ID
method: string; // HTTP Method (GET, POST, PUT, DELETE, etc.)
path: string; // Request path
headers: Record
body?: string; // Request body (optional)
binaryBody?: ArrayBuffer; // Binary request body (used by buffer_upload)
}
#### HttpResponse
`typescript`
interface HttpResponse {
statusCode: number; // HTTP Status Code (200, 404, 500, etc.)
headers?: Record
body?: string | ArrayBuffer; // Response body (string or ArrayBuffer)
}
#### ServerConfig
`typescript
interface ServerConfig {
root_dir?: string; // Static file root (Optional, as default static mount)
verbose?: boolean | 'off' | 'error' | 'warn' | 'info' | 'debug'; // Log level (default: 'off')
mime_types?: MimeTypesConfig;
mounts?: Mountable[]; // Unified mount list
}
type Mountable = WebDavMount | ZipMount | StaticMount | UploadMount | BufferUploadMount | RewriteMount | WebSocketMount;
interface WebDavMount {
type: 'webdav';
path: string; // Mount path, e.g., "/webdav"
root: string; // WebDAV root directory
}
interface ZipMount {
type: 'zip';
path: string; // Mount path, e.g., "/zip"
zip_file: string; // Zip file path
}
interface UploadMount {
type: 'upload';
path: string; // Mount path, e.g., "/upload"
temp_dir: string; // Temporary directory for uploaded files
}
interface BufferUploadMount {
type: 'buffer_upload';
path: string; // Mount path, e.g., "/buffer-upload"
}
interface RewriteMount {
type: 'rewrite';
rules: RewriteRule[];
}
interface WebSocketMount {
type: 'websocket';
path: string; // WebSocket endpoint, e.g., "/ws"
max_message_size?: number; // Max message size in bytes (default: 64MB)
}
// WebSocket Connection Request
interface WebSocketConnectionRequest {
path: string; // Connection path
query: string; // Query string
headers: Record
}
// WebSocket Connection Handler
type WebSocketConnectionHandler = (
ws: ServerWebSocket,
request: WebSocketConnectionRequest
) => void;
interface RewriteRule {
pattern: string; // Regex pattern
replacement: string; // Replacement string (supports $1, $2...)
}
interface StaticMount {
type: 'static';
path: string; // Mount path, e.g., "/images"
root: string; // Local file system directory
dir_list?: DirListConfig;
default_index?: string[];
}
type MimeTypesConfig = Record
interface DirListConfig {
enabled: boolean; // Enable directory listing
show_hidden?: boolean; // Show hidden files (default: false)
}
`
#### RequestHandler
`typescript`
type RequestHandler = (request: HttpRequest) => Promise
The request handler can return a Promise or a response object directly.
Exports the following objects and functions compatible with Node.js http module:
- createServer(requestListener?: (req: IncomingMessage, res: ServerResponse) => void): ServerServer
- classIncomingMessage
- classServerResponse
- classSTATUS_CODES
- METHODS
-
#### Streaming APIs
The library also provides low-level streaming APIs for advanced use cases:
- readRequestBodyChunk(requestId: string): Promise - Read request body in chunkswriteResponseChunk(requestId: string, chunk: string): Promise
- - Write response body in chunksendResponse(requestId: string, statusCode: number, headersJson: string): Promise
- - End streaming responsesendBinaryResponse(requestId: string, statusCode: number, headersJson: string, body: ArrayBuffer): Promise
- - Send binary response
These APIs are used internally by the Node.js compatible layer for streaming support.
``
┌─────────────────────────────────────┐
│ JavaScript / TypeScript │
│ (React Native App) │
└──────────────┬──────────────────────┘
│ Nitro Modules
┌──────────────┴──────────────────────┐
│ C++ Bridge Layer │
│ (HybridHttpServer) │
└──────────────┬──────────────────────┘
│ FFI (C ABI)
┌──────────────┴──────────────────────┐
│ Rust Core │
│ (Actix-web + Tokio) │
└─────────────────────────────────────┘
- JavaScript Layer: TypeScript, React Native
- Bridge Layer: Nitro Modules (C++)
- Core Layer: Rust (Actix-web, Tokio)
1. Request Arrival: Rust Actix-web server receives HTTP request.
2. C Callback: Calls C callback function via FFI.
3. C++ Conversion: C++ converts C struct to Nitro types.
4. JavaScript Call: Calls JavaScript handler via Nitro Modules.
5. Response Return: JavaScript returns response → C++ → C → Rust → HTTP Client.
A: Possible reasons:
1. Port In Use: Try changing the port number.
2. Insufficient Permissions: Some ports (like 80, 443) require root privileges.
3. Firewall: Check firewall settings.
A: The body field in the current version is a string type, which is not suitable for large files. Suggestions:UploadPlugin
- Use the (Recommended): Configure an upload mount. It intercepts multipart uploads, saves files to a temporary directory, and injects file paths into request headers (x-uploaded-file-path), keeping the JS payload light.BufferUploadPlugin
- Use the : Process files in memory (limit 100MB). Access data via request.binaryBody.
- Use the static file server (for downloads).
- Add streaming support in the Rust layer (advanced).
A: The current version does not directly support HTTPS. It is recommended to use a reverse proxy (like Nginx) to provide HTTPS support.
A: Built on Rust's Actix-web framework, performance is excellent. Here are the benchmark results (Test Environment: MacMini M4, 1 Thread, 2 Connections):
| Mode | QPS (Req/Sec) | Latency (Avg) |
| :--- | :--- | :--- |
| Basic HTTP | ~41.85k | ~58.14us |
| Node.js Compatible API | ~21.60k | ~274.81us |
| Koa Framework | ~13.32k | ~313.10us |
| Binary Mode | ~35.46k | ~124.29us |
Note: The Node.js compatible layer has lower performance due to additional JavaScript bridging and object conversion, but it is still sufficient for most application scenarios.
A: Yes. You can either start the dynamic server and static server separately (using different ports) or use startAppServer to provide both static file and dynamic API services on the same port.
`typescript
// Method 1: Use startAppServer (Recommended)
await server.startAppServer(8080, staticDir, apiHandler);
// Method 2: Start separately (Different ports)
await server.start(8080, handler);
// Static server on 8081
await server.startStaticServer(8081, staticDir);
`
A:
1. Check server logs (Xcode/Logcat).
2. Use getStats() to view statistics.
3. Use tools to test (curl, Postman).
`bash``Test server
curl http://localhost:8080/api/test
- 🎉 Initial release.
- ✅ Full implementation based on Nitro Modules.
- ✅ Dynamic request handling support.
- ✅ Static file serving support.
- ✅ iOS and Android support.
ISC
Issues and Pull Requests are welcome!
- Nitro Modules
- Actix-web
- React Native
---
Made with ❤️ using Rust, C++, and React Native