---
name: creating-services
description: How to create services with endpoints, middleware, and database schemas
---

# Creating Services

Services are the building blocks of a Prism Framework application. Each service is self-contained and can define endpoints, middleware, database schemas, and background jobs.

## ServiceDefinition Interface

```typescript
interface ServiceDefinition {
  /** Unique name identifying this service */
  name: string;

  /** API endpoints provided by this service */
  endpoints?: EndpointDefinition[];

  /** Express middleware scoped to specific paths */
  middleware?: MiddlewareDefinition[];

  /** SQLite database schemas, keyed by database name */
  databases?: Record<string, {
    statements: string[];  // SQL CREATE TABLE / CREATE INDEX statements
  }>;

  /** Async callback to start background jobs when the app initializes */
  startJobs?: () => Promise<void>;
}
```

All fields except `name` are optional. A service can provide any combination of endpoints, middleware, databases, and background jobs.

## Basic Service Structure

```typescript
import { ServiceDefinition, createEndpoint } from '@facetlayer/prism-framework-api';
import { z } from 'zod';

export const definition: ServiceDefinition = {
  name: 'my-service',

  // API endpoints
  endpoints: [
    // ... endpoint definitions
  ],

  // Optional middleware
  middleware: [
    // ... middleware definitions
  ],

  // Optional database schemas
  databases: {
    user: {
      statements: [
        // SQL statements for user database
      ],
    },
  },

  // Optional background jobs
  startJobs: async () => {
    // Initialize background tasks
  },
};
```

## Defining Endpoints

**Important:** Endpoint paths must NOT start with `/api`. Define paths directly (e.g. `/users`, `/users/:id`). The framework will reject any endpoint path that starts with `/api`.

Endpoints are defined with type safety using Zod schemas:

```typescript
const GetUserRequest = z.object({
  userId: z.string(),
});

const GetUserResponse = z.object({
  id: z.string(),
  email: z.string(),
  name: z.string(),
});

const getUserEndpoint = createEndpoint({
  method: 'GET',
  path: '/users/:userId',
  requestSchema: GetUserRequest,
  responseSchema: GetUserResponse,
  requires: ['authenticated-user'], // Optional requirements
  handler: async (input) => {
    // input is typed as z.infer<typeof GetUserRequest>
    const user = await getUserById(input.userId);
    return user; // Must match GetUserResponse schema
  },
});
```

### Endpoint Methods

Supported HTTP methods:
- `GET`
- `POST`
- `PUT`
- `DELETE`
- `PATCH`

### Request Data

The framework automatically combines data from:
- Request body (`req.body`)
- URL parameters (`req.params`)
- Query parameters (`req.query`)

All are merged and validated against the `requestSchema`.

### Requirements

The `requires` array can specify:
- `'authenticated-user'` - Requires an authenticated user (checks for user resource in context)

Applications can extend this by providing custom middleware or handlers.

## Server-Sent Events (SSE)

For streaming responses, return an object with a `startSse` method:

```typescript
createEndpoint({
  method: 'GET',
  path: '/stream',
  handler: async () => {
    return {
      startSse: (sse: SseResponse) => {
        // Send events
        sse.send({ message: 'Hello' });
        sse.send({ message: 'World' });

        // Close when done
        sse.close();

        // Or handle client disconnect
        sse.onClose(() => {
          console.log('Client disconnected');
        });
      },
    };
  },
});
```

## Adding Middleware

Middleware can be path-specific:

```typescript
export const definition: ServiceDefinition = {
  name: 'my-service',
  middleware: [
    {
      path: '/admin/*',
      handler: (req, res, next) => {
        // Check admin permissions
        if (!isAdmin(req)) {
          return res.status(403).json({ error: 'Forbidden' });
        }
        next();
      },
    },
  ],
};
```

## Database Schemas

Define database schemas per database:

```typescript
export const definition: ServiceDefinition = {
  name: 'users',
  databases: {
    user: {
      statements: [
        `CREATE TABLE IF NOT EXISTS users (
          id INTEGER PRIMARY KEY,
          email TEXT UNIQUE NOT NULL,
          name TEXT,
          created_at DATETIME DEFAULT (datetime('now', 'utc') || 'Z')
        )`,
        `CREATE INDEX IF NOT EXISTS idx_users_email ON users(email)`,
      ],
    },
    project: {
      statements: [
        // Project-specific tables
      ],
    },
  },
};
```

## Background Jobs

Services can start background jobs when the application initializes:

```typescript
export const definition: ServiceDefinition = {
  name: 'cleanup',
  startJobs: async () => {
    // Run periodic cleanup
    setInterval(async () => {
      await cleanupOldData();
    }, 60 * 60 * 1000); // Every hour
  },
};
```

## Error Handling

Use the built-in HTTP error classes:

```typescript
import { NotFoundError, BadRequestError } from '@facetlayer/prism-framework-api';

handler: async (input) => {
  if (!input.userId) {
    throw new BadRequestError('User ID is required');
  }

  const user = await getUserById(input.userId);
  if (!user) {
    throw new NotFoundError('User not found');
  }

  return user;
}
```

Available error classes:
- `BadRequestError` (400)
- `UnauthorizedError` (401)
- `ForbiddenError` (403)
- `NotFoundError` (404)
- `ConflictError` (409)
- `ValidationError` (422)
- `NotImplementedError` (501)
- `ServiceUnavailableError` (503)
- `HttpError` (custom status code)
