
Security News
Attackers Are Hunting High-Impact Node.js Maintainers in a Coordinated Social Engineering Campaign
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.
A simple yet powerful web framework for Bun with TypeScript, inspired by Laravel and Express
bun install katal
# Create a new project
mkdir my-katal-app
cd my-katal-app
bun init -y
bun add katal
Create a simple API server with a few endpoints:
// app.ts
import { Application, Controller } from "katal";
import type { RequestContext } from "katal";
const app = new Application({ port: 3000 });
const router = app.getRouter();
// Health check endpoint
class HealthController extends Controller {
async handle() {
return this.success({
status: "ok",
timestamp: new Date().toISOString(),
uptime: process.uptime()
});
}
}
// Get all users
class GetUsersController extends Controller {
async handle() {
return this.success([
{ id: 1, name: "John Doe", email: "john@example.com" },
{ id: 2, name: "Jane Smith", email: "jane@example.com" },
]);
}
}
// Create a new user
class CreateUserController extends Controller {
async handle(context: RequestContext) {
// In a real app, you would save to a database here
const user = {
id: Math.random().toString(36).substr(2, 9),
...context.body,
createdAt: new Date().toISOString()
};
return this.success(user, "User created successfully", 201);
}
}
// Register routes
router.get("/health", HealthController);
router.get("/users", GetUsersController);
router.post("/users", CreateUserController, {
validation: {
name: {
required: true,
type: "string",
minLength: 2,
maxLength: 100
},
email: {
required: true,
type: "email"
},
age: {
type: "number",
min: 13,
max: 120
}
},
// Optional: Add middleware to this specific route
middleware: ["log-request"]
});
// Add global middleware (applied to all routes)
import { createCorsMiddleware } from "katal";
app.use(createCorsMiddleware({
origin: "*",
methods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
allowedHeaders: ["Content-Type", "Authorization"]
}));
// Start the server
app.listen(() => {
console.log(`Server running on http://localhost:${app.port}`);
console.log(`Environment: ${app.environment}`);
});
Run your application:
bun run app.ts
Test the API:
# Health check
curl http://localhost:3000/health
# Get all users
curl http://localhost:3000/users
# Create a new user
curl -X POST http://localhost:3000/users \
-H "Content-Type: application/json" \
-d '{"name":"Alice","email":"alice@example.com","age":28}'
Controllers are the heart of your Katal application. Each controller handles exactly one route and extends the base Controller class.
import { Controller } from 'katal';
import type { RequestContext } from 'katal';
class GetUserController extends Controller {
async handle(context: RequestContext) {
const { id } = context.params;
// Access request data
const { query } = context;
// Return success response
return this.success({
id,
name: 'John Doe',
email: 'john@example.com'
});
}
}
Katal provides several response helpers in the base Controller class:
// Success response with data (returns 200 OK)
return this.success(data);
// Success with custom message
return this.success(data, 'Operation successful');
// Error response with custom status code
return this.error('User not found', 404);
// Validation error with error details
return this.validationError([{ field: 'email', message: 'Invalid email' }]);
// Redirect to another URL
return this.redirect('https://example.com');
// Custom JSON response with status code
return this.json({ custom: 'response' }, 201);
// Plain text response
return this.text('Hello, world!');
// HTML response
return this.html('<h1>Hello, world!</h1>');
Controllers support these lifecycle hooks:
class ExampleController extends Controller {
// Called before handle()
async beforeHandle(): Promise<Response | null> {
// Return null to continue
// Or return a Response to short-circuit
if (!this.isAuthenticated()) {
return this.error('Unauthorized', 401);
}
return null;
}
// Main handler
async handle(context: RequestContext) {
// Your route logic here
return this.success({ data: 'example' });
}
// Called after handle()
async afterHandle(response: Response): Promise<Response> {
// Modify response if needed
response.headers.set('X-Custom-Header', 'value');
return response;
}
}
Middleware in Katal allows you to process requests and responses. You can create middleware with before and after hooks that run around your route handlers.
interface Middleware {
before?: (context: MiddlewareContext) => Promise<Response | void> | Response | void;
after?: (context: MiddlewareContext) => Promise<Response> | Response;
}
interface MiddlewareContext {
request: Request;
response?: Response;
[key: string]: any; // For custom properties
}
Katal comes with several useful middleware:
import {
createCorsMiddleware,
createRateLimitMiddleware,
createAuthMiddleware,
Auth
} from 'katal';
// CORS middleware
app.use(createCorsMiddleware({
origin: '*',
methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'],
allowedHeaders: ['Content-Type', 'Authorization'],
credentials: true
}));
// Rate limiting (100 requests per 15 minutes per IP)
app.use(createRateLimitMiddleware({
windowMs: 15 * 60 * 1000,
maxRequests: 100
}));
// JWT Authentication
const auth = new Auth({
secret: 'your-secret-key-change-in-production',
expiresIn: '24h'
});
// Register auth service and middleware
app.singleton('auth', () => auth);
// Register auth middleware using the new registerMiddleware method
app.registerMiddleware('auth', createAuthMiddleware(auth));
const loggingMiddleware: Middleware = {
async before(context) {
const url = new URL(context.request.url);
console.log(`๐ [${new Date().toISOString()}] ${context.request.method} ${url.pathname}`);
(context.request as any)._startTime = Date.now();
return null; // Continue to next middleware/route
},
async after(context) {
const duration = Date.now() - (context.request as any)._startTime;
console.log(`โ
[${new Date().toISOString()}] ${context.request.method} ${context.request.url} - ${context.response?.status} (${duration}ms)`);
return context.response!;
}
};
// Register globally
app.use(loggingMiddleware);
const requestIdMiddleware: Middleware = {
async before(context) {
const requestId = Math.random().toString(36).substr(2, 9);
(context.request as any)._requestId = requestId;
return null;
},
async after(context) {
const requestId = (context.request as any)._requestId;
const response = context.response!;
// Add request ID to response headers
const headers = new Headers(response.headers);
headers.set('X-Request-ID', requestId);
return new Response(response.body, {
status: response.status,
statusText: response.statusText,
headers
});
}
};
// Register globally
app.use(requestIdMiddleware);
// Create a named middleware
const adminAuthMiddleware: Middleware = {
async before(context) {
const authHeader = context.request.headers.get('Authorization');
if (!authHeader || !authHeader.includes('admin-token')) {
return new Response(
JSON.stringify({ error: 'Admin access required' }),
{ status: 403, headers: { 'Content-Type': 'application/json' } }
);
}
return null;
}
};
// Register the named middleware
app.singleton('adminAuth', () => adminAuthMiddleware);
// Use in routes
router.get('/admin/dashboard', AdminDashboardController, {
middleware: ['adminAuth']
});
before hooks run first-to-lastafter hooks run last-to-firstbefore hooks run after global middlewareafter hooks run before global middleware's after hooksbeforeHandle methodhandle methodafterHandle methodKatal provides built-in error handling with the following features:
Global Error Handling:
Controller Error Helpers:
// Return a 400 Bad Request error
return this.error('Invalid input', 400);
// Return a validation error (422 Unprocessable Entity)
const errors = [
{ field: 'email', message: 'Invalid email format' },
{ field: 'password', message: 'Password too short' }
];
return this.validationError(errors);
// Return a 404 Not Found
return this.error('User not found', 404);
Custom Error Handling: For custom error handling, you can extend the base Controller class:
class BaseController extends Controller {
protected handleError(error: Error): Response {
// Log the error
console.error('Controller error:', error);
// Handle specific error types
if (error instanceof DatabaseError) {
return this.error('Database error', 503);
}
// Default error handling
return this.error('Something went wrong', 500);
}
}
Validation Errors: When using the built-in validation, validation errors are automatically handled:
// In your controller
const { valid, errors } = Validator.validate(data, validationSchema);
if (!valid) {
return this.validationError(errors);
}
This will return a response like:
{
"success": false,
"message": "Validation failed",
"errors": [
{ "field": "email", "message": "Invalid email format" },
{ "field": "password", "message": "Password too short" }
]
}
Katal allows you to create custom base controllers to encapsulate common functionality and reduce code duplication. Here are some practical examples:
abstract class AdminController extends Controller {
protected override async beforeHandle(): Promise<Response | null> {
const authHeader = this.context.request.headers.get("Authorization");
if (!authHeader || !authHeader.includes("admin-token")) {
return this.error("Admin access required", 403);
}
return null;
}
protected getAdminUser() {
return {
id: "admin-1",
name: "Admin User",
role: "admin",
};
}
}
// Usage
class AdminDashboardController extends AdminController {
async handle() {
const admin = this.getAdminUser();
return this.success({ admin, stats: { totalUsers: 1000 } });
}
}
abstract class ApiController extends Controller {
protected apiVersion = "v1";
protected override async afterHandle(response: any): Promise<any> {
if (response instanceof Response) return response;
return this.json({
version: this.apiVersion,
timestamp: new Date().toISOString(),
data: response,
});
}
}
// Usage
class StatsController extends ApiController {
async handle() {
// Returns: { version: "v1", timestamp: "...", data: { ... } }
return { requests: 1000, users: 250 };
}
}
abstract class AuthenticatedController extends Controller {
protected user: any;
protected override async beforeHandle(): Promise<Response | null> {
const authHeader = this.context.request.headers.get("Authorization");
if (!authHeader || !authHeader.startsWith("Bearer ")) {
return this.error("Authentication required", 401);
}
// In a real app, verify JWT token
this.user = await this.verifyToken(authHeader.split(" ")[1]);
return null;
}
protected getCurrentUser() {
return this.user;
}
}
abstract class CachedController extends Controller {
private static cache = new Map<string, { data: any; timestamp: number }>();
protected cacheDuration = 60000; // 1 minute
protected getCacheKey(): string {
return new URL(this.context.request.url).pathname;
}
protected override async beforeHandle(): Promise<Response | null> {
const key = this.getCacheKey();
const cached = CachedController.cache.get(key);
if (cached && Date.now() - cached.timestamp < this.cacheDuration) {
return this.success(cached.data);
}
return null;
}
protected override async afterHandle(response: any): Promise<any> {
if (response instanceof Response && response.status === 200) {
const key = this.getCacheKey();
const data = await response.clone().json();
CachedController.cache.set(key, {
data: data.data,
timestamp: Date.now(),
});
}
return response;
}
}
// Usage
class ProductsController extends CachedController {
async handle() {
// This will be cached for 1 minute
return this.success([
{ id: 1, name: "Product 1" },
{ id: 2, name: "Product 2" },
]);
}
}
abstract class LoggingController extends Controller {
protected override async beforeHandle(): Promise<Response | null> {
console.log(
`๐ [${new Date().toISOString()}] ${this.context.request.method} ${new URL(this.context.request.url).pathname}`
);
return null;
}
protected override async afterHandle(response: any): Promise<any> {
console.log(`โ
Request completed with status: ${response.status}`);
return response;
}
}
// Usage
class HealthController extends LoggingController {
async handle() {
return this.success({ status: "ok" });
}
}
Katal provides a flexible routing system that works alongside controllers. Routes are defined separately from controllers for better organization.
import { Application } from 'katal';
const app = new Application({ port: 3000 });
const router = app.getRouter();
// Basic GET route
router.get('/hello', HelloController);
// Route with URL parameters
router.get('/users/:id', GetUserController);
// Route with query parameters
// Example: /search?q=term&page=1
router.get('/search', SearchController);
// All HTTP methods are supported
router.post('/users', CreateUserController);
router.put('/users/:id', UpdateUserController);
router.delete('/users/:id', DeleteUserController);
router.patch('/users/:id', PatchUserController);
Group related routes with common middleware or path prefixes:
// Group with path prefix
router.group('/api/v1', (router) => {
router.get('/users', GetUsersController);
router.post('/users', CreateUserController);
// Nested groups
router.group('/admin', (router) => {
router.get('/dashboard', AdminDashboardController);
router.get('/stats', AdminStatsController);
}, {
middleware: ['auth', 'admin']
});
});
// Group with path prefix and options
router.group('/api/v2', (router) => {
// Inherits group middleware and validation
router.get('/profile', UserProfileController);
// Overrides group validation, merges middleware
router.post('/profile', UpdateProfileController, {
middleware: ['profile-edit'],
validation: { email: { type: 'email', required: true } }
});
}, {
middleware: ['auth'],
validation: { name: { type: 'string', required: true } }
});
/**
* You can pass a third argument to router.group for route options:
* - `middleware`: Array of middleware names to apply to all routes in the group (merged with per-route middleware).
* - `validation`: Validation schema to apply to all routes in the group (overridden by per-route validation).
*
* Per-route options will override or merge with group options as appropriate.
*/
Katal includes a powerful validation system that's easy to use and extend.
In Katal, request validation is handled using the validateRequest method in your controller. Here's how to implement validation:
import { Controller } from 'katal';
import type { RequestContext } from 'katal';
class RegisterController extends Controller {
// Define your validation schema as a class property
private readonly registerSchema = {
username: {
required: true,
type: 'string',
minLength: 3,
maxLength: 30,
pattern: /^[a-zA-Z0-9_]+$/ // Only alphanumeric and underscores
},
email: {
required: true,
type: 'email', // Built-in email type
maxLength: 255
},
age: {
type: 'number',
min: 18,
max: 120
},
isAdmin: {
type: 'boolean'
},
website: {
type: 'url', // Built-in URL type
required: false
},
role: {
type: 'string',
enum: ['user', 'editor', 'admin']
},
preferences: {
type: 'object',
required: true,
schema: {
theme: {
type: 'string',
enum: ['light', 'dark', 'system']
},
notifications: {
type: 'boolean',
required: true
},
language: {
type: 'string',
enum: ['en', 'es', 'fr', 'de']
}
}
},
tags: {
type: 'array',
min: 1,
max: 10,
items: 'string'
},
customField: {
type: 'string',
custom: (value: string) => {
// Custom validation function
// Return true if valid, false or error message if invalid
if (value.length % 2 === 0) {
return true;
}
return 'Value must have an even number of characters';
}
}
};
async handle() {
// Validate the request
const validationResponse = this.validateRequest(this.context, this.registerSchema);
if (validationResponse) {
return validationResponse;
}
// If validation passes, continue with registration logic
const { username, email, password } = this.context.body;
// Your registration logic here...
return this.success({ userId: 123 }, 'Registration successful');
}
}
// In your routes file:
router.post('/register', RegisterController);
You can create custom validation rules using the custom validator function. The function should return true if the value is valid, or a string error message if invalid.
import { Controller } from 'katal';
class UserController extends Controller {
private readonly updatePasswordSchema = {
currentPassword: {
required: true,
type: 'string'
},
newPassword: {
required: true,
type: 'string',
minLength: 8,
custom: (value: string) => {
if (typeof value !== 'string') return 'Password must be a string';
if (value.length < 8) return 'Password must be at least 8 characters';
if (!/[A-Z]/.test(value)) return 'Password must contain at least one uppercase letter';
if (!/[a-z]/.test(value)) return 'Password must contain at least one lowercase letter';
if (!/\d/.test(value)) return 'Password must contain at least one number';
return true; // Validation passed
}
},
confirmPassword: {
required: true,
type: 'string',
custom: (value: string, data: any) => {
if (value !== data.newPassword) {
return 'Passwords do not match';
}
return true;
}
}
};
async updatePassword() {
const validation = this.validateRequest(this.context, this.updatePasswordSchema);
if (validation) return validation;
// Continue with password update logic
const { currentPassword, newPassword } = this.context.body;
// ...
return this.success({ message: 'Password updated successfully' });
}
}
Katal provides several built-in validators through the type property:
'string' - Validates string values'number' - Validates numeric values'boolean' - Validates boolean values'email' - Validates email format'url' - Validates URL format'array' - Validates arrays (use items to validate array elements)'object' - Validates objects (use schema to validate object properties)Example using built-in validators:
const schema = {
username: {
type: 'string',
minLength: 3,
maxLength: 30,
pattern: /^[a-z0-9_]+$/
},
email: {
type: 'email',
required: true
},
age: {
type: 'number',
min: 18,
max: 120
},
roles: {
type: 'array',
items: {
type: 'string',
enum: ['user', 'editor', 'admin']
},
min: 1
},
metadata: {
type: 'object',
schema: {
lastLogin: { type: 'string' },
preferences: { type: 'object' }
}
}
};
Katal provides built-in JWT authentication through the Auth class. Here's how to implement it in your application.
import { Auth, createAuthMiddleware } from 'katal';
// Initialize auth with your secret key
const auth = new Auth({
secret: process.env.JWT_SECRET || 'your-secret-key',
expiresIn: '24h' // Token expiration
});
// Register auth service in the container
app.singleton('auth', () => auth);
// Register auth middleware
const middlewareManager = (app as any).middlewareManager;
middlewareManager.register('auth', createAuthMiddleware(auth));
class RegisterController extends Controller {
async handle(context: RequestContext) {
const { email, password, name } = context.body;
const auth = app.resolve<Auth>('auth');
// Hash the password
const hashedPassword = await auth.hashPassword(password);
// In a real app, save to database
const user = {
id: Math.random().toString(36).substr(2, 9),
email,
name,
password: hashedPassword,
};
// Generate JWT token
const token = await auth.generateToken(user);
return this.success({
user: {
id: user.id,
email: user.email,
name: user.name
},
token,
});
}
}
class LoginController extends Controller {
async handle(context: RequestContext) {
const { email, password } = context.body;
const auth = app.resolve<Auth>('auth');
// In a real app, fetch user from database
const user = {
id: "123",
email: "demo@example.com",
name: "Demo User",
password: await auth.hashPassword("password123"),
};
// Verify password
const isValid = await auth.verifyPassword(password, user.password);
if (!isValid) {
return this.error("Invalid credentials", 401);
}
// Generate token
const token = await auth.generateToken(user);
return this.success({
user: {
id: user.id,
email: user.email,
name: user.name
},
token,
});
}
}
// Public route
router.get('/public', PublicController);
// Protected route - requires authentication
router.get('/profile', ProfileController, {
middleware: ['auth']
});
// Protected route with role check
class AdminDashboardController extends Controller {
async handle() {
// Access the authenticated user from context
const user = this.context.user;
// Check user role
if (user.role !== 'admin') {
return this.error('Admin access required', 403);
}
return this.success({
message: 'Welcome to the admin dashboard',
stats: { /* ... */ }
});
}
}
// Register admin route
router.get('/admin/dashboard', AdminDashboardController, {
middleware: ['auth']
});
The Auth class provides these methods:
// Hash a password
const hashed = await auth.hashPassword('mypassword');
// Verify a password
const isValid = await auth.verifyPassword('mypassword', hashed);
// Generate JWT token
const token = await auth.generateToken({ id: '123', role: 'user' });
// Verify token (done automatically by middleware)
const payload = await auth.verifyToken(token);
Include the token in the Authorization header:
GET /protected-route
Authorization: Bearer your-jwt-token-here
Or as a query parameter:
GET /protected-route?token=your-jwt-token-here
For implementing token refresh, create a refresh token endpoint:
class RefreshTokenController extends Controller {
async handle() {
const auth = app.resolve<Auth>('auth');
const user = this.context.user; // From current token
// Generate new token with extended expiration
const newToken = await auth.generateToken({
id: user.id,
role: user.role
});
return this.success({ token: newToken });
}
}
// Register refresh token route
router.post('/auth/refresh', RefreshTokenController, {
middleware: ['auth'] // Requires a valid (but possibly expired) token
});
}
}
// Login class LoginController extends Controller { async handle({ body }) { const { email, password } = body;
// Find user by email
const user = await User.findOne({ email });
if (!user) {
return this.error('Invalid credentials', 401);
}
// Verify password
const isValid = await Bun.password.verify(password, user.password);
if (!isValid) {
return this.error('Invalid credentials', 401);
}
// Generate JWT token
const token = auth.signToken({ userId: user.id });
// Set auth cookie
this.setCookie('auth_token', token, {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'lax',
maxAge: 60 * 60 * 24 * 7 // 7 days
});
return this.success({
user: {
id: user.id,
username: user.username,
email: user.email
}
}, 'Login successful');
}
}
### ๐ Logging
Katal provides a powerful and flexible logging system through the `LoggingIntegration` class, allowing you to log messages at different severity levels and route them to various destinations.
#### Log Levels
Katal supports four log levels:
```typescript
enum LogLevel {
DEBUG = "debug", // Detailed debug information
INFO = "info", // General application flow
WARN = "warn", // Warnings that don't prevent execution
ERROR = "error" // Errors that need attention
}
import { LoggingIntegration, LogLevel } from 'katal';
// Create a logger instance
const logger = new LoggingIntegration()
.setMinLevel(LogLevel.INFO) // Set minimum log level
.toConsole(); // Log to console
// Log messages
await logger.debug('Debug message', { some: 'context' });
await logger.info('User logged in', { userId: 123, ip: '192.168.1.1' });
await logger.warn('API rate limit approaching', { endpoint: '/api/users' });
await logger.error('Database connection failed', new Error('Connection timeout'));
Katal comes with built-in destinations and allows custom ones:
// Log to console with colors (default)
logger.toConsole();
// Log to a file
logger.toFile('logs/app.log');
// Custom destination example (e.g., to a remote service)
const remoteLogging = {
async write(entry) {
await fetch('https://logs.example.com/api', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(entry)
});
}
};
logger.addDestination(remoteLogging);
Here's how to create a request logging middleware:
import { LoggingIntegration } from 'katal';
const logger = new LoggingIntegration()
.setMinLevel(LogLevel.INFO)
.toConsole()
.toFile('logs/requests.log');
const requestLogger = {
async before(context) {
// Store start time for duration calculation
(context.request as any)._startTime = Date.now();
// Log request start
await logger.info('Request started', {
method: context.request.method,
url: context.request.url,
ip: context.request.headers.get('x-forwarded-for') ||
context.request.headers.get('x-real-ip') ||
context.request.headers.get('cf-connecting-ip') ||
'unknown'
});
return null; // Continue to next middleware
},
async after(context) {
const { request, response } = context;
const duration = Date.now() - (request as any)._startTime;
// Log request completion
await logger.info('Request completed', {
method: request.method,
url: request.url,
status: response?.status,
duration: `${duration}ms`,
'response-size': response?.headers.get('content-length') || 'unknown'
});
return response;
}
};
// Register middleware
app.use(requestLogger);
For comprehensive error handling and logging:
// Global error handler
app.onError = async (error: Error, context: any) => {
await logger.error('Unhandled error', error, {
url: context?.request?.url,
method: context?.request?.method,
params: context?.params,
query: context?.query,
body: context?.body
});
return new Response(
JSON.stringify({
error: 'Internal Server Error',
requestId: context?.requestId
}),
{ status: 500, headers: { 'Content-Type': 'application/json' } }
);
};
// In a controller
class UserController extends Controller {
async handle() {
try {
// Your code here
} catch (error) {
// Log the error with context
await logger.error('Failed to process user', error, {
userId: this.context.params.id,
action: 'updateProfile'
});
// Return error response
return this.error('Failed to process request', 500);
}
}
}
Deploying your Katal application is straightforward. Here's how to deploy to various platforms:
# Install dependencies
bun install
# Start development server with hot reloading
bun run dev
# Install production dependencies (with --production flag)
bun install --production
# Build your application (if needed)
bun run build
# Start production server
bun start
Create a .env file in your project root:
NODE_ENV=production
PORT=3000
JWT_SECRET=your-secure-secret
DATABASE_URL=your-database-url
Install PM2 globally:
bun add -g pm2
Create an ecosystem file ecosystem.config.js:
module.exports = {
apps: [{
name: 'my-katal-app',
script: 'app.js',
instances: 'max',
exec_mode: 'cluster',
env: {
NODE_ENV: 'production',
PORT: 3000
}
}]
};
Start your application:
pm2 start ecosystem.config.js
Create a Dockerfile:
# Use the official Bun image
FROM oven/bun:latest
# Set working directory
WORKDIR /app
# Copy package.json and bun.lockb
COPY package.json bun.lockb ./
# Install dependencies
RUN bun install --production
# Copy the rest of the application
COPY . .
# Build the application (if needed)
# RUN bun run build
# Expose the port the app runs on
EXPOSE 3000
# Start the application
CMD ["bun", "start"]
Build and run the Docker container:
# Build the image
docker build -t my-katal-app .
# Run the container
docker run -p 3000:3000 --env-file .env my-katal-app
Katal can be deployed to any platform that supports Node.js applications:
Katal includes a simple yet powerful dependency injection container:
// Register a service
app.singleton('database', () => new Database(process.env.DATABASE_URL));
// In a controller
class UserController extends Controller {
private database = this.app.make('database');
async handle() {
const users = await this.database.query('SELECT * FROM users');
return this.success(users);
}
}
Check out the examples/ directory for complete examples:
examples/simple/ - Basic API exampleexamples/custom-base-controllers/ - Extending base controllersexamples/middleware-hooks/ - Middleware and hooks examplesContributions are welcome! Please read our contributing guide to get started.
This project is licensed under the MIT License - see the LICENSE file for details.
MIT
FAQs
A simple yet powerful web framework for Bun with TypeScript
The npm package katal receives a total of 7 weekly downloads. As such, katal popularity was classified as not popular.
We found that katal 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
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.

Security News
Axios compromise traced to social engineering, showing how attacks on maintainers can bypass controls and expose the broader software supply chain.

Security News
Node.js has paused its bug bounty program after funding ended, removing payouts for vulnerability reports but keeping its security process unchanged.