apiv
v1.2.1
Published
A Hapi.js plugin that automatically adds version prefixes to all your API routes, making it easy to version your API endpoints.
Maintainers
Readme
Apiv
A Hapi.js plugin that automatically adds version prefixes to all your API routes, making it easy to version your API endpoints.
Installation
npm install apivFeatures
- Automatically prefixes all routes with a version and/or API prefix
- Global configuration applies to all routes
- Simple setup with sensible defaults
- Supports custom prefixes and version strings
- Can be disabled globally when needed
- TypeScript support included
Usage
Basic Example
By default, the plugin prefixes all routes with /api/v1:
import Hapi from '@hapi/hapi'
import Apiv from 'apiv'
const server = Hapi.server({ port: 3000 })
await server.register({ plugin: Apiv })
server.route({
method: 'GET',
path: '/users',
handler: () => ({ users: [] })
})
await server.start()
// Route is now accessible at: GET /api/v1/usersCustom Version and Prefix
You can customize both the version and prefix:
await server.register({
plugin: Apiv,
options: {
version: 'v2',
prefix: 'service'
}
})
server.route({
method: 'GET',
path: '/users',
handler: () => ({ users: [] })
})
// Route is now accessible at: GET /service/v2/usersEmpty Prefix or Version
You can set either option to an empty string to exclude it:
// Only version, no prefix
await server.register({
plugin: Apiv,
options: {
prefix: '',
version: 'v1'
}
})
// Routes accessible at: GET /v1/users
// Only prefix, no version
await server.register({
plugin: Apiv,
options: {
prefix: 'api',
version: ''
}
})
// Routes accessible at: GET /api/users
// Neither (routes unchanged)
await server.register({
plugin: Apiv,
options: {
prefix: '',
version: ''
}
})
// Routes accessible at: GET /usersDisabling the Plugin
You can disable the plugin entirely by setting enabled: false:
await server.register({
plugin: Apiv,
options: { enabled: false }
})
server.route({
method: 'GET',
path: '/users',
handler: () => ({ users: [] })
})
// Route remains at: GET /users (no prefix applied)Configuration
Global Options
All configuration is done at the plugin registration level:
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| version | string | 'v1' | Version string to prepend to routes (max 255 chars) |
| prefix | string | 'api' | Prefix string to prepend before version (max 255 chars) |
| enabled | boolean | true | Whether to enable the plugin |
Important Notes
- Per-Route Overrides Supported (via aliases): Add
options.plugins.apivto a route to create an extra alias path with a differentprefixand/orversion. The global path still exists. - Global Prefix Remains: The plugin sets a global prefix for all routes; per-route overrides add aliases and do not replace the global path.
- Per-Route Disable (alias): Use
options.plugins.apiv: falseor{ enabled: false }to add an unprefixed alias for that route. The globally prefixed path remains unless the plugin is disabled globally.
Route-Level Overrides
You can add route-specific overrides using options.plugins.apiv. These do not modify the original route registration; they create additional alias paths at server startup.
Version Override
server.route({
method: 'GET',
path: '/users',
options: { plugins: { apiv: { version: 'v2' } } },
handler: () => ({ users: [] })
})
// Aliases:
// - Global: GET /api/v1/users
// - Override: GET /api/v2/usersPrefix Override
server.route({
method: 'GET',
path: '/users',
options: { plugins: { apiv: { prefix: 'service' } } },
handler: () => ({ users: [] })
})
// Aliases:
// - Global: GET /api/v1/users
// - Override: GET /service/v1/usersPrefix + Version Override
server.route({
method: 'GET',
path: '/users',
options: { plugins: { apiv: { prefix: 'service', version: 'v3' } } },
handler: () => ({ users: [] })
})
// Aliases:
// - Global: GET /api/v1/users
// - Override: GET /service/v3/usersEmpty Version Override
server.route({
method: 'GET',
path: '/users',
options: { plugins: { apiv: { version: '' } } },
handler: () => ({ users: [] })
})
// Aliases:
// - Global: GET /api/v1/users
// - Override: GET /api/usersPer-Route Disable
// Disable via boolean
server.route({
method: 'GET',
path: '/health',
options: { plugins: { apiv: false } },
handler: () => ({ status: 'ok' })
})
// Disable via object
server.route({
method: 'GET',
path: '/status',
options: { plugins: { apiv: { enabled: false } } },
handler: () => ({ status: 'ok' })
})
// Aliases:
// - Global: GET /api/v1/health, /api/v1/status
// - Unprefixed: GET /health, /statusRoute Options Preservation
Important: Alias routes created by per-route overrides or disabling only copy the route's handler function. Route configuration options are NOT preserved on the alias routes.
This means that settings like:
auth- authentication requirementsvalidate- request validation schemasdescription,notes,tags- documentation metadatacors- CORS settingspayload- payload parsing optionstimeout- timeout settings- Other route-level configurations
...will only apply to the original prefixed route, not to any aliases.
Example
server.route({
method: 'GET',
path: '/secure',
options: {
auth: 'jwt', // Only applies to /api/v1/secure
validate: {
query: Joi.object({
id: Joi.string().required()
})
},
plugins: { apiv: false } // Creates alias at /secure
},
handler: () => ({ data: 'sensitive' })
})
// Result:
// - GET /api/v1/secure - Has auth and validation
// - GET /secure - No auth, no validation (just the handler)If you need authentication, validation, or other options on multiple paths, you should explicitly define separate routes instead of relying on aliases:
// Better approach for routes requiring auth/validation
const routeConfig = {
auth: 'jwt',
validate: {
query: Joi.object({ id: Joi.string().required() })
}
}
server.route({
method: 'GET',
path: '/api/v1/secure',
options: routeConfig,
handler: secureHandler
})
server.route({
method: 'GET',
path: '/secure',
options: routeConfig,
handler: secureHandler
})Examples
Multiple Versions on Same Server
If you need to support multiple API versions, you can register separate server instances or use different plugins:
// All routes get v1 prefix
await server.register({
plugin: Apiv,
options: { version: 'v1' }
})
server.route({
method: 'GET',
path: '/users',
handler: () => ({ version: 1 })
})
// Accessible at: GET /api/v1/usersNested Paths
The plugin works with any route path structure:
server.route({
method: 'GET',
path: '/users/{id}/posts',
handler: () => ({ posts: [] })
})
// Accessible at: GET /api/v1/users/{id}/postsRoot Path
Even the root path gets prefixed:
server.route({
method: 'GET',
path: '/',
handler: () => ({ message: 'API Root' })
})
// Accessible at: GET /api/v1TypeScript
The plugin includes TypeScript definitions:
import { Server } from '@hapi/hapi'
import Apiv, { ApiVersionPluginOptions } from 'apiv'
const server: Server = Hapi.server({ port: 3000 })
const options: ApiVersionPluginOptions = {
version: 'v2',
prefix: 'api',
enabled: true
}
await server.register({ plugin: Apiv, options })License
MIT
