Axum N-API Bridge
This library provides a macro to easily expose an Axum web server, written in Rust, to a Node.js application using NAPI-rs.
This allows you to write high-performance, memory-safe web services in Rust and seamlessly integrate them into a Node.js environment.
✅ Production-Ready with Phusion Passenger
axum_napi_bridge is fully tested and compatible with Phusion Passenger, the industry-standard application server for production Node.js deployments. The library supports both:
- Nginx + Passenger - High-performance reverse proxy with automatic process management
- Apache + Passenger - Full-featured web server with enterprise-grade features
All Passenger configurations are thoroughly tested in our CI/CD pipeline using Docker containers to ensure production reliability.
How it Works
The library provides a macro, napi_axum_bridge!, which generates the necessary N-API boilerplate to bridge your Axum Router to Node.js. It creates an exported function handle_request that can be called from JavaScript with request details.
Getting Started
Prerequisites
Usage
-
Set up your
Cargo.tomlYou will need to add this library and its dependencies to your
Cargo.toml. Because the bridge uses macros from several other crates, you need to include them as direct dependencies in your project.[] = "my-axum-app" = "0.1.0" = "2021" [] = ["cdylib"] [] = "0.8.4" = { = "1", = ["full"] } # The bridge library = { = "https://github.com/your-repo/axum-napi-bridge" } # Or use a path dependency # Dependencies required by the bridge's macros = { = "1.0", = ["derive"] } = "1.0" = { = "3.0.0", = ["tokio_rt", "serde-json"] } = "3.0.0" [] = "2" -
Use the macro in your Rust code
In your
src/lib.rs(or another source file likesrc/bridge.rs), define a function that returns your AxumRouter, and then pass it to thenapi_axum_bridge!macro.use get; use napi_axum_bridge; // This generates the bridge code napi_axum_bridge!; -
Set up your
package.jsonYou will need a
package.jsonto manage the build process. -
Build and run
You can now
requirethe generated.nodefile in your JavaScript code and use thehandleRequestfunction.
Example
For a working example, you can create the following files in a new project.
Cargo.toml
[]
= "example-axum-app"
= "0.1.0"
= "2021"
= "build.rs"
[]
= ["cdylib"]
= "src/bridge.rs"
[]
= "0.8.4"
= { = "1", = ["full"] }
= "0.1.0"
= { = "1.0", = ["derive"] }
= "1.0"
= { = "3.0.0", = ["tokio_rt", "serde-json"] }
= "3.0.0"
[]
= "2"
src/bridge.rs
use get;
use napi_axum_bridge;
use Duration;
use sleep;
napi_axum_bridge!;
build.rs
extern crate napi_build;
package.json
tests/bridge.spec.ts
import { test, expect } from '@playwright/test'
import { handleRequest } from '../index.js'
test('GET /', async () => {
const response = await handleRequest('GET', '/', null, null)
const parsed = JSON.parse(response)
expect(parsed.status).toBe(200)
expect(parsed.body).toBe('Hello from the example app!')
})
test('GET /test', async () => {
const response = await handleRequest('GET', '/test', null, null)
const parsed = JSON.parse(response)
expect(parsed.status).toBe(200)
expect(parsed.body).toBe('This is a test route.')
})
test('POST /test', async () => {
const response = await handleRequest('POST', '/test', null, null)
const parsed = JSON.parse(response)
expect(parsed.status).toBe(200)
expect(parsed.body).toBe('POST response from test route.')
})
server.ts (Optional HTTP Server)
import { handleRequest } from './index.js'
import { createServer } from 'http'
const server = createServer(async (req, res) => {
try {
const body = await new Promise<string | null>((resolve) => {
let data = ''
req.on('data', (chunk) => (data += chunk))
req.on('end', () => resolve(data || null))
})
const result = await handleRequest(req.method!, req.url!, req.headers, body)
const response = JSON.parse(result)
res.writeHead(response.status, response.headers)
res.end(response.body)
} catch (error) {
res.writeHead(500, { 'Content-Type': 'text/plain' })
res.end('Internal Server Error')
}
})
server.listen(process.env.PORT || 3000)
console.log(`Server listening on port ${process.env.PORT || 3000}`)
Development
Pre-commit Hook
To ensure code quality and prevent issues, install the pre-commit hook that runs all tests before commits:
The hook automatically runs:
- Main library tests
- Sample app tests
- Performance benchmarks
- Phusion Passenger deployment tests (Nginx + Apache)
All tests must pass before commits are allowed.
Development Commands
# Install pre-commit hook
# Build the library
# Run tests
# Format code
# Lint code
# Run benchmarks
Production Deployment with Phusion Passenger
Nginx + Passenger
The bridge is fully compatible with Nginx + Passenger for high-performance production deployments:
// server.ts
import { handleRequest } from './index.js'
import { createServer } from 'http'
const server = createServer(async (req, res) => {
try {
const body = await new Promise<string | null>((resolve) => {
let data = ''
req.on('data', (chunk) => (data += chunk))
req.on('end', () => resolve(data || null))
})
const result = await handleRequest(req.method!, req.url!, req.headers, body)
const response = JSON.parse(result)
res.writeHead(response.status, response.headers)
res.end(response.body)
} catch (error) {
res.writeHead(500, { 'Content-Type': 'text/plain' })
res.end('Internal Server Error')
}
})
server.listen(process.env.PORT || 3000)
console.log(`Server listening on port ${process.env.PORT || 3000}`)
Apache + Passenger
The bridge also works seamlessly with Apache + Passenger for enterprise environments requiring advanced web server features.
Docker Deployment
Pre-built Docker configurations are available in the repository:
Dockerfile.passenger- Nginx + Passenger setupDockerfile.apache- Apache + Passenger setup
Both configurations use official Phusion Passenger base images and are tested in CI/CD.