v 0.1.97

API Handlers

Ecopages provides a straightforward way to define API endpoints within your application. This allows you to build server-side logic, fetch data, or perform actions directly from your Ecopages project.

Defining Handlers

You define API handlers using methods on your EcopagesApp instance, typically in your app.ts file. Each method corresponds to an HTTP verb (GET, POST, etc.).

// filepath: app.ts
import { EcopagesApp } from '@ecopages/core/adapters/bun/create-app';
import appConfig from './eco.config';
 
const app = new EcopagesApp({ appConfig });
 
// Define a GET handler
app.get('/api/greet', async ({ response }) => {
	return response.text('Hello from the API!');
});
 
// Define a POST handler
app.post('/api/submit', async ({ request, response }) => {
	const body = await request.json(); // Assuming JSON body
	console.log('Received data:', body);
	return response.json({ success: true, received: body });
});
 
await app.start();

Handler Context

Every API handler function receives a HandlerContext object as its argument. This object provides access to essential information and utilities for handling the request and constructing the response.

app.get('/api/example/:id', async ({ request, response }) => {
	const { id } = request.params;
	const userAgent = request.headers.get('user-agent');
	const queryParam = request.query.get('search');
	const body = {
		userId: id,
		agent: userAgent,
		query: queryParam,
	};
 
	return response.status(200).json(body);
});

The HandlerContext contains:

Accessing the Server Instance

You can use the server property in the handler context for advanced use cases. For example, to get the request IP address:

app.get('/api/hello', async ({ response, request, server }) => {
	return response.json({
		message: 'Hello world!',
		requestIp: server.requestIP(request),
	});
});

The ApiResponseBuilder

Instead of manually creating new Response(...) objects, Ecopages provides the ApiResponseBuilder via context.response. This utility simplifies response creation with a fluent API.

Basic Usage

app.get('/api/data', async ({ response }) => {
	const data = { message: 'Here is your data' };
	// Automatically sets Content-Type to application/json
	return response.json(data);
});
 
app.get('/api/plain', async ({ response }) => {
	// Automatically sets Content-Type to text/plain
	return response.text('Plain text response.');
});
 
app.get('/api/html-page', async ({ response }) => {
	// Automatically sets Content-Type to text/html
	return response.html('<h1>Hello HTML</h1>');
});

Chaining Methods

You can chain methods to customize the response status and headers before sending the body.

app.post('/api/create', async ({ response }) => {
	// ... creation logic ...
	const newItem = { id: 123, name: 'New Item' };
 
	return response
		.status(201) // Set status to 201 Created
		.headers({ 'X-Custom-Header': 'CreatedValue' }) // Add custom headers
		.json(newItem); // Send JSON body
});

Available Methods

app.get('/api/old-path', async ({ response }) => {
	// Permanent redirect (301)
	return response.status(301).redirect('/api/new-path');
});
 
app.get('/api/find/:id', async ({ request, response }) => {
	const { id } = request.params;
	const item = // ... find item logic ...
 
	if (!item) {
		// Send a 404 error with a JSON body
		return response.error({ message: `Item ${id} not found` }, 404);
	}
 
	return response.json(item);
});

Using the ApiResponseBuilder makes your API handler code cleaner, more readable, and less prone to errors compared to manually constructing Response objects.

WebSocket Handlers

If your application requires real-time bidirectional communication, you can define WebSocket handlers when starting your application. Ecopages inherits Bun's powerful WebSocket implementation.

await app.start({
    websocket: {
        open(ws) {
            console.log('Client connected');
        },
        message(ws, message) {
            console.log('Received:', message);
            ws.send(`Server received: ${message}`);
        },
    },
});

Route Groups

Use app.group() to organize related routes under a shared prefix with optional middleware. This is useful for sections like admin panels or authenticated APIs.

import { EcopagesApp } from '@ecopages/core/adapters/bun/create-app';
import * as admin from './handlers/admin';
import { authMiddleware } from './middleware/auth';
 
const app = new EcopagesApp({ appConfig });
 
app.group(
	'/admin',
	(r) => {
		r.get('/', admin.list);
		r.get('/new', admin.newPost);
		r.post('/posts', admin.createPost);
		r.get('/posts/:id', admin.editPost);
		r.post('/posts/:id', admin.updatePost);
		r.post('/posts/:id/delete', admin.deletePost);
		r.post('/upload', admin.uploadImage);
	},
	{
		middleware: [authMiddleware],
	},
);
 
await app.start();

The group registration:

Type Safety with Define Handlers

For enhanced type safety with path parameters, middleware context, and request schemas, use the defineApiHandler and defineGroupHandler helpers.

See the Define Handlers documentation for complete details.