
Security News
Google’s OSV Fix Just Added 500+ New Advisories — All Thanks to One Small Policy Change
A data handling bug in OSV.dev caused disputed CVEs to disappear from vulnerability feeds until a recent fix restored over 500 advisories.
@rhinolabs/boilr
Advanced tools
A convention-based Fastify framework with batteries included. Boilr brings Next.js-inspired simplicity and TypeScript type safety to Fastify API development.
# Create a new project using the CLI (recommended)
npm install -g @rhinolabs/boilr-cli
boilr new my-api
cd my-api
npm install
npm run dev
# Or add to an existing project
npm install @rhinolabs/boilr
// server.ts - Your entry point
import { createApp } from '@rhinolabs/boilr';
// Create the application with configuration
const app = createApp({
server: { port: 3000 },
routes: { dir: './routes' },
plugins: {
swagger: {
info: {
title: 'My API',
version: '1.0.0'
}
}
}
});
// Start the server
app.start();
// routes/hello.ts - A simple route
import { z } from 'zod';
import { defineSchema, type GetHandler } from '@rhinolabs/boilr';
export const schema = defineSchema({
get: {
querystring: z.object({
name: z.string().optional()
}),
response: {
200: z.object({
message: z.string()
})
}
}
});
export const get: GetHandler<typeof schema> = async (request, reply) => {
const { name = 'world' } = request.query;
return { message: `Hello, ${name}!` };
};
Boilr automatically maps your directory structure to API routes following Next.js conventions:
routes/
├── index.ts → GET /
├── products/
│ ├── index.ts → GET/POST /products
│ ├── [id].ts → GET/PUT/DELETE /products/:id
│ └── [id]/reviews.ts → GET /products/:id/reviews
├── (admin)/ → (admin) won't affect URL paths
│ └── settings.ts → GET /settings
└── [...catch-all].ts → Catch remaining routes
Define your routes with full type safety using Zod schemas:
// routes/users/[id].ts
import { z } from 'zod';
import { defineSchema, GetHandler, PutHandler } from '@rhinolabs/boilr';
export const schema = defineSchema({
get: {
params: z.object({
id: z.string().transform(val => parseInt(val, 10))
}),
response: {
200: z.object({
id: z.number(),
name: z.string()
})
}
},
put: {
params: z.object({
id: z.string().transform(val => parseInt(val, 10))
}),
body: z.object({
name: z.string().min(1)
}),
response: {
200: z.object({
id: z.number(),
name: z.string(),
updated: z.boolean()
})
}
}
});
// Type-safe GET handler (id is correctly typed as number)
export const get: GetHandler<typeof schema> = async (request, reply) => {
const { id } = request.params;
return { id, name: `User ${id}` };
};
// Type-safe PUT handler (with typed body and params)
export const put: PutHandler<typeof schema> = async (request, reply) => {
const { id } = request.params;
const { name } = request.body;
return { id, name, updated: true };
};
Define handlers for different HTTP methods by exporting named functions:
export async function get(request, reply) { ... } // GET
export async function post(request, reply) { ... } // POST
export async function put(request, reply) { ... } // PUT
export async function patch(request, reply) { ... } // PATCH
export async function del(request, reply) { ... } // DELETE
Customize your application with a flexible configuration system:
const app = createApp({
server: {
port: 8080,
host: '0.0.0.0',
logger: true
},
routes: {
dir: './api',
prefix: '/api/v1'
},
plugins: {
helmet: true,
rateLimit: {
max: 100,
timeWindow: '1 minute'
},
cors: true,
swagger: {
info: {
title: 'My API',
description: 'API documentation',
version: '1.0.0'
}
}
}
});
Boilr automatically adds error response schemas to your Swagger documentation. By default, all routes include a 500 (Internal Server Error) response schema, but you can customize this behavior:
const app = createApp({
exceptions: {
// Customize which error status codes to include by default
defaultErrorStatusCodes: [400, 401, 404, 500],
// Custom error response format
formatter: (exception, request, reply) => ({
success: false,
error: exception.message,
code: exception.statusCode
}),
// Custom error schema for documentation
formatterSchema: z.object({
success: z.boolean(),
error: z.string(),
code: z.number()
})
}
});
Override error schemas for specific routes by adding defaultErrorStatusCodes
to your method schemas:
export const schema = defineSchema({
get: {
// Include specific error codes for this endpoint
defaultErrorStatusCodes: [401, 403, 404],
response: {
200: z.object({ data: z.string() })
}
},
post: {
// Disable automatic error schemas for this method
defaultErrorStatusCodes: false,
body: z.object({ name: z.string() }),
response: {
201: z.object({ id: z.number() })
}
}
});
The default error response schema matches the built-in error format:
{
statusCode: number; // HTTP status code
message: string; // Error message
error: string; // Error type (e.g., "NotFound")
details?: unknown; // Optional error details
}
This ensures your API documentation always includes comprehensive error response information, making it easier for API consumers to understand and handle errors properly.
Boilr provides comprehensive error handling with built-in HTTP exception classes and automatic error formatting:
import { NotFoundException } from '@rhinolabs/boilr';
throw new NotFoundException('User not found');
Available Exception Classes:
Client Errors (4xx):
BadRequestException
(400) - Invalid request format or parametersUnauthorizedException
(401) - Authentication required or invalidForbiddenException
(403) - Insufficient permissionsNotFoundException
(404) - Resource not foundMethodNotAllowedException
(405) - HTTP method not supportedNotAcceptableException
(406) - Requested format not acceptableRequestTimeoutException
(408) - Request took too longConflictException
(409) - Resource conflict or duplicateGoneException
(410) - Resource no longer availablePreconditionFailedException
(412) - Precondition not metPayloadTooLargeException
(413) - Request payload too largeUnsupportedMediaTypeException
(415) - Media type not supportedImATeapotException
(418) - I'm a teapot (RFC 2324)UnprocessableEntityException
(422) - Validation failedValidationException
(422) - Validation failed with detailed errorsServer Errors (5xx):
InternalServerErrorException
(500) - Internal server errorNotImplementedException
(501) - Feature not implementedBadGatewayException
(502) - Bad gateway responseServiceUnavailableException
(503) - Service temporarily unavailableGatewayTimeoutException
(504) - Gateway timeoutHttpVersionNotSupportedException
(505) - HTTP version not supportedthrow new NotFoundException('User not found', {
name: 'USER_NOT_FOUND', // Custom error code
details: { userId: id }, // Additional context
cause: originalError // Underlying error
});
import { ValidationException } from '@rhinolabs/boilr';
// Manual validation errors
throw new ValidationException('Validation failed', [
{ field: 'email', message: 'Invalid email format', value: 'invalid-email' },
{ field: 'age', message: 'Must be a positive number', value: -5 }
]);
// Zod validation errors are automatically converted
const result = userSchema.parse(invalidData); // Throws ZodError -> becomes ValidationException
All exceptions are automatically formatted into a consistent JSON response:
{
"statusCode": 404,
"message": "User not found",
"error": "NotFound",
"details": { "userId": "123" }
}
Configure global error handling behavior:
import { createApp } from '@rhinolabs/boilr';
const app = createApp({
exceptions: {
// Custom error formatter
formatter: (exception, request, reply) => ({
success: false,
code: exception.statusCode,
message: exception.message,
timestamp: new Date().toISOString(),
path: request.url,
data: exception.details
}),
// Enable/disable error logging (default: true)
logErrors: true
}
});
Errors are automatically logged with different levels:
Log format includes:
{
"timestamp": "2024-01-01T00:00:00.000Z",
"level": "error",
"message": "User not found",
"statusCode": 404,
"path": "/api/users/123",
"method": "GET",
"details": { "userId": "123" }
}
Boilr includes a flexible authentication system that supports multiple authentication methods and can be applied selectively to routes. The system automatically extracts tokens/credentials and passes them to your validators.
First, declare your authentication context interface globally:
// types/auth.ts or in your main file
declare global {
namespace Boilr {
interface AuthContext {
user: {
id: string;
email: string;
role: 'admin' | 'user';
};
}
}
}
Configure authentication methods with type-specific validators:
import { createApp } from '@rhinolabs/boilr';
const app = createApp({
auth: {
methods: [
// Bearer Token Authentication
{
name: 'jwt',
type: 'bearer', // Validator: (request, token: string | undefined) => AuthContext
default: true, // Applied to all routes by default
validator: async (request, token) => {
if (!token) throw new UnauthorizedException('Bearer token required');
const user = await verifyJwtToken(token);
return { user, authMethod: 'jwt' };
}
},
// API Key Authentication
{
name: 'apikey',
type: 'apiKey', // Validator: (request, apiKey: string | undefined) => AuthContext
options: { key: 'x-api-key', location: 'header' },
default: false, // Only applied when explicitly specified
validator: async (request, apiKey) => {
if (!apiKey) throw new UnauthorizedException('API key required');
const user = await validateApiKey(apiKey);
return { user, authMethod: 'apikey' };
}
},
// Cookie Authentication
{
name: 'session',
type: 'cookie', // Validator: (request, cookieValue: string | undefined) => AuthContext
options: { key: 'sessionId', location: 'cookie' },
validator: async (request, sessionId) => {
if (!sessionId) throw new UnauthorizedException('Session required');
const user = await getSessionUser(sessionId);
return { user, authMethod: 'session' };
}
},
// Basic Authentication
{
name: 'basic',
type: 'basic', // Validator: (request, username?: string, password?: string) => AuthContext
validator: async (request, username, password) => {
if (!username || !password) throw new UnauthorizedException('Credentials required');
const user = await validateCredentials(username, password);
return { user, authMethod: 'basic' };
}
}
]
}
});
Apply authentication to routes using the auth
field in your schema:
// routes/protected.ts
import { z } from 'zod';
import { defineSchema, GetHandler } from '@rhinolabs/boilr';
export const schema = defineSchema({
get: {
// Uses only auth methods with default: true
// (no auth field = apply default methods)
// Or specify specific auth methods
auth: ['jwt', 'apikey'],
// Or disable auth for this route (even if globally configured)
auth: false,
response: {
200: z.object({
message: z.string(),
user: z.object({
id: z.number(),
name: z.string()
})
})
}
}
});
export const get: GetHandler<typeof schema> = async (request) => {
// Access typed authenticated context
const { user } = request.ctx; // Fully typed based on your AuthContext
return {
message: `Hello ${user.email}!}`,
user
};
};
Each authentication type provides a specific validator signature:
'bearer'
: (request: FastifyRequest, token: string | undefined) => AuthContext
'apiKey'
: (request: FastifyRequest, apiKey: string | undefined) => AuthContext
'cookie'
: (request: FastifyRequest, cookieValue: string | undefined) => AuthContext
'basic'
: (request: FastifyRequest, username: string | undefined, password: string | undefined) => AuthContext
The system automatically extracts tokens/credentials using the built-in extractors and passes them to your validators. You focus on validation logic, not extraction.
Check out complete examples:
MIT
FAQs
Convention-based Fastify framework with batteries included
We found that @rhinolabs/boilr demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
A data handling bug in OSV.dev caused disputed CVEs to disappear from vulnerability feeds until a recent fix restored over 500 advisories.
Research
/Security News
175 malicious npm packages (26k+ downloads) used unpkg CDN to host redirect scripts for a credential-phishing campaign targeting 135+ organizations worldwide.
Security News
Python 3.14 adds template strings, deferred annotations, and subinterpreters, plus free-threaded mode, an experimental JIT, and Sigstore verification.