npm package: @sujit_rajan/express-route-forge
Description: Production-grade Express router factory — fluent API, typed middleware, Zod validation, versioning, and route introspection. Used across microservices.
Author: HomeServe Engineering By Sujith Rajan
Latest version: 1.0.2

# express-route-forge

Production-grade Express router factory. Fluent API, full TypeScript, Zod validation, API versioning, and live route introspection — used identically across every microservice.

## Install

```bash
npm install express-route-forge
# Peer deps
npm install express
# Optional — for schema validation
npm install zod
```

---

## Quick start

```ts
import { route, createRouter, asyncHandler, validateBody } from 'express-route-forge';
import { z } from 'zod';
import { sendOtp, verifyOtp, getMe } from './controllers/auth.controller';
import { protect } from './middleware/auth.middleware';

const sendOtpSchema = z.object({
  phone: z.string().regex(/^\+?[1-9]\d{7,14}$/),
  role:  z.enum(['customer', 'worker']).default('customer'),
});

export const authRouter = createRouter(
  [
    route('post', '/auth/send-otp', asyncHandler(sendOtp))
      .middleware(validateBody(sendOtpSchema))
      .describe('Send a 6-digit OTP to the given phone number')
      .tag('public')
      .build(),

    route('post', '/auth/verify-otp', asyncHandler(verifyOtp))
      .describe('Verify OTP and return a signed JWT token')
      .tag('public')
      .build(),

    route('get', '/auth/me', asyncHandler(getMe))
      .middleware(protect)
      .describe('Return the currently authenticated user')
      .tag('protected')
      .build(),
  ],
  { version: 'v1' },
);

// In app.ts
app.use(authRouter);
```

---

## API

### `route(method, path, handler)`

Entry point for the fluent builder.

```ts
route('post', '/auth/login', loginHandler)
  .middleware(rateLimiter, validateBody(schema))  // runs before handler
  .describe('Authenticate and return a JWT')      // shown in /routes
  .tag('auth', 'public')                          // for filtering
  .version('v2')                                  // override router version
  .build()                                        // returns RouteDefinition
```

---

### `createRouter(routes, options?)`

Registers an array of `RouteDefinition`s on an Express `Router`.

```ts
const router = createRouter(routes, {
  version:           'v1',          // prefixes every path: /v1/auth/login
  globalMiddlewares: [requestLogger], // applied to every route in this router
});
```

Returns a `ForgeRouter` (Express `Router` + `_routeMeta` for introspection).

---

### `asyncHandler(fn)`

Wraps an async handler so errors reach your global error middleware instead of crashing Node.

```ts
route('get', '/me', asyncHandler(getMe))
```

---

### `validateBody(schema)` · `validateQuery(schema)` · `validateParams(schema)`

Returns a `RequestHandler` that validates the respective part of the request against any Zod schema (or any object with a `safeParse` method).

On **failure**: returns `422` with per-field errors.
On **success**: replaces the validated portion with coerced data.

```ts
const schema = z.object({ phone: z.string(), role: z.enum(['customer','worker']) });

route('post', '/send-otp', sendOtp)
  .middleware(validateBody(schema))
  .build()
```

---

### `routeListHandler(routers, options?)`

Returns an Express handler that responds with a JSON list of every registered route.

```ts
// Mount on a protected admin endpoint
app.get('/v1/routes', protect, restrictTo('admin'), routeListHandler([authRouter, bookingRouter]));

// Filter by tag
routeListHandler([authRouter], { tag: 'public' })
```

**Response:**
```json
{
  "success": true,
  "total": 3,
  "routes": [
    {
      "method": "POST",
      "path": "/v1/auth/send-otp",
      "description": "Send a 6-digit OTP to the given phone number",
      "tags": ["public"],
      "middlewares": ["otpLimiter", "validateBody"]
    }
  ]
}
```

---

### `extractRoutes(routers)`

Returns the same `RouteInfo[]` without an HTTP response — useful for startup logging or OpenAPI generation.

```ts
import { extractRoutes } from 'express-route-forge';

const routes = extractRoutes([authRouter, bookingRouter, workerRouter]);
console.table(routes);
```

---

## Adding a route

```ts
// append one block — nothing else changes
route('delete', '/auth/sessions/:id', asyncHandler(revokeSession))
  .middleware(protect, validateParams(z.object({ id: z.string().uuid() })))
  .describe('Revoke a specific session by ID')
  .tag('protected')
  .build(),
```

## Adding a whole new service router

```ts
// booking.routes.ts
import { route, createRouter, asyncHandler, validateBody } from 'express-route-forge';

export const bookingRouter = createRouter([
  route('post', '/bookings', asyncHandler(createBooking))
    .middleware(protect, validateBody(createBookingSchema))
    .describe('Create a new service booking')
    .tag('bookings')
    .build(),
  // ...
], { version: 'v1' });
```

Same package, same pattern, zero duplication across services.

---

## Run tests

```bash
npm test
npm test -- --coverage
```

---

## Publish to npm

```bash
npm run build        # compiles to dist/ (CJS + ESM + .d.ts)
npm publish          # runs prepublishOnly: build + test first
```

For a scoped private package:

```bash
# package.json → "name": "@homeserve/express-route-forge"
npm publish --access restricted
```

---

## License

MIT