Skip to content

REST API

Representational State Transfer (REST) is an architectural style for exposing and consuming resources over HTTP. Following well-considered conventions keeps such APIs consistent, maintainable, and pleasant to work with.

All terminology follows the Effect HTTP API Reference and the OpenAPI 3.1.0 Specification. Where the two sources clash or fall short, the following table provides clarity — settling naming conflicts and introducing new terms.

Resource
An abstraction representing a domain concept — either a single item or a collection.

Designing an API means defining the contract of every resource interaction and organizing those contracts into an API Definition, API Groups, and API Endpoints. Each API Endpoint is fulfilled by an API Handler as a separate concern — owning the logic but not the contract.

API Definition (1)
└── API Group (1..N)
└── API Endpoint (1..N)
└ ─ ─ API Handler (1)

The API Definition serves as the source of truth, drives the generated OpenAPI specification, and enforces type safety across all API Handlers.

  1. Create an API Definition file:

    • Directoryapplications
      • Directorygateway
        • Directorysrc
          • Directoryapi
            • api.definition.ts
  2. Scaffold it based on the following snippet:

    import { HttpApi } from 'effect/unstable/httpapi';
    export const ApiDefinition = HttpApi.make('Api');

The API Group gathers API Endpoints that operate on related resources, keeping their contracts defined in one place.

  1. Locate the API Definition file:

    • Directoryapplications
      • Directorygateway
        • Directorysrc
          • Directoryapi
            • api.definition.ts
  2. Add an API Group based on the following snippet:

    import { HttpApi, HttpApiGroup } from 'effect/unstable/httpapi';
    const ApiGroupPlayers = HttpApiGroup.make('Players');
    export const ApiDefinition = HttpApi.make('Api').add(ApiGroupPlayers);

The API Endpoint defines the contract for a specific interaction with a resource.

  1. Locate the API Definition file:

    • Directoryapplications
      • Directorygateway
        • Directorysrc
          • Directoryapi
            • api.definition.ts
  2. Add an API Endpoint based on the following snippet:

    import {
    HttpApi,
    HttpApiEndpoint,
    HttpApiGroup,
    } from 'effect/unstable/httpapi';
    const ApiGroupPlayers = HttpApiGroup.make('Players').add(
    HttpApiEndpoint.post('create-player', '/players'),
    HttpApiEndpoint.get('get-players', '/players'),
    HttpApiEndpoint.get('get-player', '/players/:id'),
    HttpApiEndpoint.patch('update-player', '/players/:id'),
    HttpApiEndpoint.put('replace-player', '/players/:id'),
    HttpApiEndpoint.delete('delete-player', '/players/:id'),
    );
    export const ApiDefinition = HttpApi.make('Api').add(ApiGroupPlayers);
  3. Extend the API Endpoint based on the following snippet:

    import {
    CreatePlayerError,
    CreatePlayerPayload,
    CreatePlayerRequestHeaders,
    CreatePlayerSuccess,
    } from '@applications/gateway/src/api/players/create-player.schema.ts';
    import {
    DeletePlayerError,
    DeletePlayerParameters,
    DeletePlayerRequestHeaders,
    DeletePlayerSuccess,
    } from '@applications/gateway/src/api/players/delete-player.schema.ts';
    import {
    GetPlayerError,
    GetPlayerParameters,
    GetPlayerRequestHeaders,
    GetPlayerSuccess,
    } from '@applications/gateway/src/api/players/get-player.schema.ts';
    import {
    GetPlayersError,
    GetPlayersQuery,
    GetPlayersRequestHeaders,
    GetPlayersSuccess,
    } from '@applications/gateway/src/api/players/get-players.schema.ts';
    import {
    ReplacePlayerError,
    ReplacePlayerParameters,
    ReplacePlayerPayload,
    ReplacePlayerRequestHeaders,
    ReplacePlayerSuccess,
    } from '@applications/gateway/src/api/players/replace-player.schema.ts';
    import {
    UpdatePlayerError,
    UpdatePlayerParameters,
    UpdatePlayerPayload,
    UpdatePlayerRequestHeaders,
    UpdatePlayerSuccess,
    } from '@applications/gateway/src/api/players/update-player.schema.ts';
    import {
    HttpApi,
    HttpApiEndpoint,
    HttpApiGroup,
    } from 'effect/unstable/httpapi';
    const ApiGroupPlayers = HttpApiGroup.make('Players').add(
    HttpApiEndpoint.post('create-player', '/players', {
    headers: CreatePlayerRequestHeaders,
    payload: CreatePlayerPayload,
    success: CreatePlayerSuccess,
    error: CreatePlayerError,
    }),
    HttpApiEndpoint.get('get-players', '/players', {
    headers: GetPlayersRequestHeaders,
    query: GetPlayersQuery,
    success: GetPlayersSuccess,
    error: GetPlayersError,
    }),
    HttpApiEndpoint.get('get-player', '/players/:id', {
    headers: GetPlayerRequestHeaders,
    params: GetPlayerParameters,
    success: GetPlayerSuccess,
    error: GetPlayerError,
    }),
    HttpApiEndpoint.patch('update-player', '/players/:id', {
    headers: UpdatePlayerRequestHeaders,
    params: UpdatePlayerParameters,
    payload: UpdatePlayerPayload,
    success: UpdatePlayerSuccess,
    error: UpdatePlayerError,
    }),
    HttpApiEndpoint.put('replace-player', '/players/:id', {
    headers: ReplacePlayerRequestHeaders,
    params: ReplacePlayerParameters,
    payload: ReplacePlayerPayload,
    success: ReplacePlayerSuccess,
    error: ReplacePlayerError,
    }),
    HttpApiEndpoint.delete('delete-player', '/players/:id', {
    headers: DeletePlayerRequestHeaders,
    params: DeletePlayerParameters,
    success: DeletePlayerSuccess,
    error: DeletePlayerError,
    }),
    );
    export const ApiDefinition = HttpApi.make('Api').add(ApiGroupPlayers);

The API Handler fulfills a single API Endpoint. It holds the actual logic — receiving the declared input and producing the declared output.