Ecopages0.2.0-rc.4

Server API

Ecopages provides a powerful server API that allows you to create endpoints and handle server-side logic with ease.

Basic Usage

Create an app.ts file in your project root:

import { createApp } from '@ecopages/core/create-app';
import appConfig from './eco.config';
 
const app = await createApp({ appConfig });
 
app.get('/api/hello', async () => {
	return new Response('Hello World');
});
 
await app.start();

Starting the server

Call app.start() after you register routes. It boots the server for the current CLI mode (dev, preview, build, or start).

Pass an optional callback to run logic once the server can accept traffic. The callback receives { origin } — the resolved base URL with no trailing slash.

await app.start(({ origin }) => {
	// readiness hook
});

The OnAppStartCallback type is exported from @ecopages/core/create-app.

Note: In embedded mode (for example the Vite plugin), app.start() registers the callback but does not bind a port — the host owns the listen socket and runs your callback when the app is ready. Pass runtime: { embedded: true, devClientOwner: 'host' } to createApp(). See Build and Host.

HTTP Methods

The server supports all standard HTTP methods:

app.get('/api/resource', handler);
app.post('/api/resource', handler);
app.put('/api/resource', handler);
app.patch('/api/resource', handler);
app.delete('/api/resource', handler);
app.options('/api/resource', handler);
app.head('/api/resource', handler);

Route Parameters

You can define dynamic route parameters using the :param syntax:

app.get('/api/users/:id/posts/:postId', async ({ params }) => {
	const { id, postId } = params;
	return new Response(JSON.stringify({ userId: id, postId }));
});

Type Safety

Ecopages leverages TypeScript to provide strong type safety for your API handlers, especially for route parameters.

Handlers can be defined directly, but keeping the params type aligned with the path string requires explicit manual typing. Use defineApiHandler to infer parameter types from the path literal and keep route definitions organized.

import { defineApiHandler } from '@ecopages/core';
 
const getUserHandler = defineApiHandler({
	method: 'GET',
	path: '/api/users/:id',
	handler: async ({ params }) => {
		const { id } = params;
		const user = { id: id, name: 'Example User' };
 
		if (!user) {
			return new Response('User not found', { status: 404 });
		}
		return new Response(JSON.stringify(user));
	},
});
 
app.add(getUserHandler);
 
const updateUserHandler = defineApiHandler({
	method: 'PUT',
	path: '/api/users/:id',
	handler: async ({ params }) => {
		const { id } = params;
		return new Response(JSON.stringify({ id, message: 'User updated' }));
	},
});
 
app.add(updateUserHandler);

Benefits of Using defineApiHandler

Using defineApiHandler offers several advantages:

  • Automatic Parameter Typing: Eliminates the need to manually type params.
  • Improved Readability: Keeps the handler definition concise.
  • Enhanced Organization: Encourages defining handlers as separate, reusable constants before registering them.
  • Reduced Errors: Prevents type mismatches between the path string and the parameters used in the handler.

Handler Context: Accessing the Server Instance

The handler context now includes a server property, which exposes the underlying server instance (such as Bun's server object). This allows you to access advanced utilities, such as retrieving the request IP address or triggering a server reload.

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

You can use server for advanced debugging, runtime methods, or accessing server-specific utilities.

Static Site Generation

During static site generation, the server can still handle API requests, making it possible to generate static content from API responses:

import { eco } from '@ecopages/core';
 
export default eco.page({
	staticProps: async ({ appConfig }) => {
		const response = await fetch(`${appConfig.baseUrl}/api/data`);
		const data = await response.json();
		
		return {
			props: { data },
		};
	},
	render: ({ data }) => <pre>{JSON.stringify(data)}</pre>
});

Development Server

The development server includes:

  • Hot Module Replacement (HMR)
  • Automatic route reloading
  • API endpoint hot reloading
  • Static file serving
  • Error handling with detailed stack traces

Production Build

When building for production:

  1. Static routes are pre-rendered
  2. API routes are preserved for server-side handling
  3. Assets are optimized and collected
  4. Development-only code is stripped

Error Handling

The server includes built-in error handling:

app.get('/api/error', async () => {
	throw new Error('Something went wrong');
	// Will return a 500 response with error details in development
	// and a safe error message in production
});

WebSocket Support

Ecopages provides a runtime-agnostic WebSocket API with dynamic segment support, per-connection context, and automatic HTTP-to-WebSocket upgrades. Works on both Bun and Node.

See WebSockets for the full guide.

Testing API Endpoints

Use app.fetch() to exercise handlers without a bound network server. This is the same request surface embedded hosts use.

import { createApp } from '@ecopages/core/create-app';
import appConfig from './eco.config';
 
const app = await createApp({ appConfig });
 
app.get('/api/hello', async ({ response }) => {
	return response.json({ message: 'Hello World' });
});
 
const response = await app.fetch(new Request('http://localhost/api/hello'));
const data = await response.json();
 
console.log(data); // { message: 'Hello World' }

Configuration

Server configuration can be customized in your eco.config.ts:

import { ConfigBuilder } from '@ecopages/core/config-builder';
import { ecopagesJsxPlugin } from '@ecopages/ecopages-jsx';
 
const config = await new ConfigBuilder() 
	.setBaseUrl('http://localhost:3000') 
	// ... other config
	.setIntegrations([ecopagesJsxPlugin()])
	.build();