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. Passruntime: { embedded: true, devClientOwner: 'host' }tocreateApp(). 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:
- Static routes are pre-rendered
- API routes are preserved for server-side handling
- Assets are optimized and collected
- 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();