npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

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.

Readme

Quality Gate Status Bugs Code Smells Duplicated Lines (%) Coverage Known Vulnerabilities

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 apiv

Features

  • 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/users

Custom 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/users

Empty 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 /users

Disabling 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.apiv to a route to create an extra alias path with a different prefix and/or version. 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: false or { 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/users

Prefix Override

server.route({
  method: 'GET',
  path: '/users',
  options: { plugins: { apiv: { prefix: 'service' } } },
  handler: () => ({ users: [] })
})
// Aliases:
// - Global:   GET /api/v1/users
// - Override: GET /service/v1/users

Prefix + 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/users

Empty Version Override

server.route({
  method: 'GET',
  path: '/users',
  options: { plugins: { apiv: { version: '' } } },
  handler: () => ({ users: [] })
})
// Aliases:
// - Global:   GET /api/v1/users
// - Override: GET /api/users

Per-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, /status

Route 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 requirements
  • validate - request validation schemas
  • description, notes, tags - documentation metadata
  • cors - CORS settings
  • payload - payload parsing options
  • timeout - 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/users

Nested 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}/posts

Root Path

Even the root path gets prefixed:

server.route({
  method: 'GET',
  path: '/',
  handler: () => ({ message: 'API Root' })
})
// Accessible at: GET /api/v1

TypeScript

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