Skip to content

πŸš€ A Vite plugin for adding Node.js API routes to your Vite + Vue project. JSON-only, single-port backend and frontend, similar to Next.js API routes but for Vite.

License

Notifications You must be signed in to change notification settings

ibnushahraa/vite-node-api

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

4 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

vite-node-api

npm version npm downloads license CI coverage

πŸš€ A Vite plugin for adding Node.js API routes to your Vite + Vue project. JSON-only, single-port backend and frontend, similar to Next.js API routes but for Vite.


✨ Features

  • File-based routing - /api/hello.js β†’ /api/hello
  • Single-port deployment - Dev and production on one port
  • JSON-only API - Auto-parse request body and query params
  • Hot reload - API changes auto-reload in dev mode
  • Security built-in - Path traversal protection, body limits, timeouts
  • CORS support - Optional CORS headers configuration
  • Production-ready - ESBuild bundling with minification
  • TypeScript support - Full TypeScript definitions included
  • Zero config - Works out of the box with sensible defaults

πŸ“¦ Installation

npm install vite-node-api

πŸš€ Quick Start

1. Configure Vite

// vite.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import viteNodeApi from 'vite-node-api'

export default defineConfig({
  plugins: [
    vue(),
    viteNodeApi({
      apiDir: 'server/api',  // optional, default: 'server/api'
      port: 4173,            // optional, default: 4173
      cors: true             // optional, default: false
    })
  ]
})

2. Create API Route

// server/api/hello.js
export default async (req, res) => {
  return {
    message: 'Hello from API!',
    method: req.method,
    query: req.query
  }
}

3. Run Development Server

npm run dev

Visit http://localhost:5173/api/hello β†’ {"message":"Hello from API!","method":"GET","query":{}}

4. Build for Production

npm run build

This creates:

  • dist/client/ - Frontend files
  • dist/server/ - Bundled API routes + runtime

5. Deploy to Production

Build for Production

npm run build

This creates:

dist/
β”œβ”€β”€ client/           # Frontend static files (HTML, CSS, JS)
β”œβ”€β”€ server/           # Backend bundled files
β”‚   β”œβ”€β”€ entry.mjs    # Production runtime (standalone)
β”‚   └── server/      # API routes (preserves folder structure)
β”‚       └── api/
└── .env             # Auto-copied from .env.production

Key Features:

  • βœ… Standalone - All dependencies bundled, no node_modules needed
  • βœ… Environment variables - .env.production auto-copied to dist/.env
  • βœ… Folder structure preserved - server/api/ stays as dist/server/server/api/

Run Production Server

# Simple - just run the entry file
node dist/server/entry.mjs

OR with custom environment variables:

# Override .env values
PORT=3000 node dist/server/entry.mjs

Both frontend and API run on the same port (default: 4173).

Deploy to Server

Upload only the dist/ folder:

# 1. Build locally
npm run build

# 2. Upload dist/ to your server
scp -r dist/ user@server:/var/www/myapp/

# 3. On server, run it
ssh user@server
cd /var/www/myapp/dist
node server/entry.mjs

No npm install needed - everything is bundled!


πŸ“– Usage Examples

Dynamic Route Parameters

// server/api/users/[id].js
export default async (req, res) => {
  const { id } = req.params

  const user = { id, name: 'Alice', email: 'alice@example.com' }

  return user
}

Request: GET /api/users/123

Response: {"id":"123","name":"Alice","email":"alice@example.com"}

Query Parameters

// server/api/search.js
export default async (req, res) => {
  const { q, limit = 10 } = req.query

  return {
    query: q,
    limit: parseInt(limit),
    results: []
  }
}

Request: GET /api/search?q=hello&limit=5

Response: {"query":"hello","limit":5,"results":[]}

POST Request with JSON Body

// server/api/users/create.js
export default async (req, res) => {
  const { name, email } = req.body

  // Validate input
  if (!name || !email) {
    res.statusCode = 400
    return { error: 'Name and email required' }
  }

  // Simulate database insert
  const newUser = {
    id: Date.now(),
    name,
    email,
    createdAt: new Date().toISOString()
  }

  return newUser
}

Request:

curl -X POST http://localhost:5173/api/users/create \
  -H "Content-Type: application/json" \
  -d '{"name":"Bob","email":"bob@example.com"}'

Response: {"id":1704067200000,"name":"Bob","email":"bob@example.com","createdAt":"2025-01-01T00:00:00.000Z"}

Nested Routes

server/api/
β”œβ”€β”€ hello.js              β†’ /api/hello
β”œβ”€β”€ users/
β”‚   β”œβ”€β”€ list.js           β†’ /api/users/list
β”‚   β”œβ”€β”€ create.js         β†’ /api/users/create
β”‚   └── [id].js           β†’ /api/users/[id]
└── posts/
    β”œβ”€β”€ index.js          β†’ /api/posts/index
    └── [slug].js         β†’ /api/posts/[slug]

Manual Response Control

// server/api/custom.js
export default async (req, res) => {
  // Set custom headers
  res.setHeader('X-Custom-Header', 'value')

  // Set status code
  res.statusCode = 201

  // Manual response (won't auto-JSON encode)
  res.setHeader('Content-Type', 'text/plain')
  res.end('Custom response')

  // No return needed when manually writing response
}

Async Operations

// server/api/posts/list.js
export default async (req, res) => {
  // Simulate async database query
  await new Promise(resolve => setTimeout(resolve, 100))

  const posts = [
    { id: 1, title: 'First Post' },
    { id: 2, title: 'Second Post' }
  ]

  return posts
}

βš™οΈ Configuration

Plugin Options

viteNodeApi({
  // Directory containing API route files
  // Default: 'server/api'
  apiDir: 'server/api',

  // Port for production runtime
  // Default: 4173 (Vite preview default)
  port: 3000,

  // Maximum request body size in bytes
  // Default: 1000000 (1MB)
  bodyLimit: 5_000_000,

  // Request timeout in milliseconds
  // Default: 30000 (30s)
  timeout: 60000,

  // Enable CORS headers
  // Default: false
  cors: true,

  // Or configure CORS with custom origin
  cors: {
    origin: 'https://example.com'
  }
})

Environment Variables

vite-node-api automatically handles environment variables for both development and production:

Development (.env.development):

# Loaded by Vite dev server
VITE_APP_API_KEY=dev-key-123
DATABASE_URL=postgresql://localhost/mydb

Production (.env.production):

# Auto-copied to dist/.env during build
VITE_APP_API_KEY=prod-key-456
DATABASE_URL=postgresql://production/mydb

Using in API routes:

// server/api/data.js
export default async (req, res) => {
  const apiKey = process.env.VITE_APP_API_KEY
  const dbUrl = process.env.DATABASE_URL

  // Your logic here
  return { status: 'ok' }
}

Key Points:

  • βœ… Development: Vite automatically loads .env.development
  • βœ… Production: .env.production is auto-copied to dist/.env during build
  • βœ… Runtime: Production server auto-loads dist/.env on startup
  • βœ… Override: Can override env vars when running: PORT=3000 node dist/server/entry.mjs

Request Object

API route handlers receive an enhanced request object:

{
  method: string                // HTTP method: GET, POST, PUT, PATCH, DELETE
  url: string                   // Full request URL
  headers: IncomingHttpHeaders
  body?: any                    // Parsed JSON body (POST/PUT/PATCH only)
  query: Record<string, string> // Parsed query parameters (?key=value)
  params: Record<string, string> // Dynamic route parameters ([id])
  // ... all standard Node.js IncomingMessage properties
}

Response Object

Standard Node.js ServerResponse with helper methods:

export default async (req, res) => {
  // Set status code
  res.statusCode = 404

  // Set headers
  res.setHeader('Content-Type', 'application/json')

  // Return data (auto-JSON encoded)
  return { error: 'Not found' }

  // Or manually send response
  res.end(JSON.stringify({ error: 'Not found' }))
}

πŸ”’ Security

Path Traversal Protection

Built-in protection against path traversal attacks:

βœ… /api/users/list      β†’ server/api/users/list.js
❌ /api/../../../etc/passwd  β†’ 403 Forbidden

Body Size Limits

Default 1MB limit for request bodies (configurable):

viteNodeApi({
  bodyLimit: 5_000_000  // 5MB
})

Requests exceeding the limit return 413 Request Entity Too Large.

Request Timeouts

Default 30s timeout per request (configurable):

viteNodeApi({
  timeout: 60000  // 60s
})

Requests exceeding the timeout return 408 Request Timeout.

CORS Configuration

// Simple CORS (allow all origins)
viteNodeApi({
  cors: true
})

// Custom origin
viteNodeApi({
  cors: {
    origin: 'https://example.com'
  }
})

πŸ—οΈ Project Structure

my-vite-app/
β”œβ”€β”€ src/
β”‚   β”œβ”€β”€ main.js         # Frontend entry
β”‚   └── App.vue
β”œβ”€β”€ server/
β”‚   └── api/
β”‚       β”œβ”€β”€ hello.js    # API routes
β”‚       β”œβ”€β”€ users/
β”‚       β”‚   └── list.js
β”‚       └── posts/
β”‚           └── [id].js
β”œβ”€β”€ vite.config.js      # Vite config with plugin
└── package.json

After build:

dist/
β”œβ”€β”€ client/             # Frontend files (served by entry.mjs)
β”‚   β”œβ”€β”€ index.html
β”‚   β”œβ”€β”€ assets/
β”‚   └── ...
└── server/             # Backend files
    β”œβ”€β”€ entry.mjs       # Production runtime server
    └── api/            # Bundled API routes
        β”œβ”€β”€ hello.js
        └── users/
            └── list.js

πŸš€ Deployment

Node.js

# Build
npm run build

# Run production server
node dist/server/entry.mjs

Docker

FROM node:20-alpine

WORKDIR /app

COPY package*.json ./
RUN npm ci --production

COPY dist ./dist

EXPOSE 4173

CMD ["node", "dist/server/entry.mjs"]

Environment Variables

# Override port
PORT=3000 node dist/server/entry.mjs

# Or use plugin config env variable
VITE_NODE_API_PORT=3000 node dist/server/entry.mjs

# Override timeout
VITE_NODE_API_TIMEOUT=60000 node dist/server/entry.mjs

Process Managers

PM2:

pm2 start dist/server/entry.mjs --name my-api

Systemd:

[Unit]
Description=Vite Node API Server

[Service]
ExecStart=/usr/bin/node /path/to/dist/server/entry.mjs
Restart=always
Environment=PORT=4173

[Install]
WantedBy=multi-user.target

πŸ§ͺ Testing

npm test

Test coverage includes:

  • Plugin configuration
  • API routing
  • Security (path traversal, body limits)
  • Build process
  • Error handling

See test/ directory for examples.


⚑ Performance

Run benchmarks with autocannon:

npm run bench

Benchmark Results

Performance with fast-json-stringify and fast-json-parse optimization (100 concurrent connections, 10s duration):

Endpoint Req/sec Latency Throughput
Simple GET 3,331 29.53ms ~720 KB/s
GET with Query 2,281 43.20ms ~500 KB/s
Complex JSON 2,965 33.25ms ~1.0 MB/s
POST with Body 2,146 46.07ms ~480 KB/s

Key optimizations:

  • ⚑ +17% faster on simple GET requests
  • ⚑ +7% faster on POST with JSON body parsing
  • πŸš€ Pre-compiled JSON schemas for error responses
  • πŸ“¦ Smart fallback to native JSON for complex objects

Results may vary based on hardware and system load. Run npm run bench on your system for accurate measurements.

See benchmark/ directory for more details.


πŸ“‚ API Reference

Handler Function

type ApiHandler = (
  req: ApiRequest,
  res: ApiResponse
) => Promise<any> | any

Return value is automatically JSON-encoded. If you manually send a response, don't return anything.

Error Handling

Errors are automatically caught and returned as JSON:

export default async (req, res) => {
  throw new Error('Something went wrong')
  // Returns: {"error":"Something went wrong"}
  // Status: 500
}

Status Codes

// Success
res.statusCode = 200  // OK (default)
res.statusCode = 201  // Created
res.statusCode = 204  // No Content

// Client Errors
res.statusCode = 400  // Bad Request
res.statusCode = 401  // Unauthorized
res.statusCode = 403  // Forbidden
res.statusCode = 404  // Not Found

// Server Errors
res.statusCode = 500  // Internal Server Error

πŸ“š Examples

For more detailed examples and use cases, please check the examples folder:

  • Configuration examples (basic, CORS, custom options)
  • API route examples (GET, POST, query parameters, validation)
  • Complete documentation and usage guides

πŸ“„ License

MIT Β© 2025

About

πŸš€ A Vite plugin for adding Node.js API routes to your Vite + Vue project. JSON-only, single-port backend and frontend, similar to Next.js API routes but for Vite.

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published