Guides

Data API

Generate automatic CRUD endpoints from database tables with customizable routes, authentication, and middleware in AntelopeJS.

Prerequisites

The Data API builds on both the API and database systems. Install all three interface packages in your module.

npm install @antelopejs/interface-data-api @antelopejs/interface-api @antelopejs/interface-database-decorators

Import from these packages as needed:

  • @antelopejs/interface-data-api -- DataController, RegisterDataController, DefaultRoutes
  • @antelopejs/interface-api -- Controller
  • @antelopejs/interface-database-decorators -- Table definitions and models

Before using the Data API, you need a registered table. Refer to the Database guide for table setup.

Creating a Data Controller

The DataController function generates a controller class with CRUD endpoints mapped to a database table. It accepts three arguments: the table class, a route definition object, and a base controller. The @RegisterDataController() decorator registers the generated controller with the framework.

src/data-api/tasks.ts
import { DataController, RegisterDataController, DefaultRoutes } from "@antelopejs/interface-data-api";
import { Controller } from "@antelopejs/interface-api";
import { Task } from "../db/tables/task";

@RegisterDataController()
class TaskDataController extends DataController(
  Task,
  {
    get: DefaultRoutes.Get,
    list: DefaultRoutes.List,
    new: DefaultRoutes.New,
    edit: DefaultRoutes.Edit,
    delete: DefaultRoutes.Delete,
  },
  Controller("/api/tasks")
) {}

This single class definition creates five endpoints:

RouteMethodDescription
GET /api/tasks/get?id=<id>DefaultRoutes.GetRetrieve a single task
GET /api/tasks/listDefaultRoutes.ListList all tasks
POST /api/tasks/newDefaultRoutes.NewCreate a new task
PUT /api/tasks/edit?id=<id>DefaultRoutes.EditUpdate an existing task
DELETE /api/tasks/delete?id=<id>DefaultRoutes.DeleteDelete a task

The final path segment matches the key you used in the route definition (get, list, new, edit, delete). Identifiers for single-record routes are passed as the id query parameter, not as a path segment.

DataController is a function that returns a class, not a decorator. Use extends DataController(...) to create your data controller.

Using DefaultRoutes.All

When you need all five CRUD routes, DefaultRoutes.All provides a shorthand that includes Get, List, New, Edit, and Delete in a single declaration.

src/data-api/tasks.ts
import { DataController, RegisterDataController, DefaultRoutes } from "@antelopejs/interface-data-api";
import { Controller } from "@antelopejs/interface-api";
import { Task } from "../db/tables/task";

@RegisterDataController()
class TaskDataController extends DataController(
  Task,
  DefaultRoutes.All,
  Controller("/api/tasks")
) {}

The shorthand produces the exact same set of endpoints as the explicit route definition. Use the explicit form when you need to include only specific routes or apply different options to each route.

Selective Routes

You can include only the routes your API needs by listing a subset in the route definition. Omitted routes are not generated.

src/data-api/readonly-tasks.ts
import { DataController, RegisterDataController, DefaultRoutes } from "@antelopejs/interface-data-api";
import { Controller } from "@antelopejs/interface-api";
import { Task } from "../db/tables/task";

@RegisterDataController()
class ReadOnlyTaskController extends DataController(
  Task,
  {
    get: DefaultRoutes.Get,
    list: DefaultRoutes.List,
  },
  Controller("/api/public/tasks")
) {}

This controller exposes only GET endpoints. No create, update, or delete operations are available, making it suitable for public or read-only APIs.

Adding Authentication

@Authentication from @antelopejs/interface-auth works as a class decorator. Applied to the Data API controller, it registers an auth check as a computed property on the controller, which the framework evaluates for every incoming request before any handler runs.

src/data-api/tasks.ts
import { DataController, RegisterDataController, DefaultRoutes } from "@antelopejs/interface-data-api";
import { Controller } from "@antelopejs/interface-api";
import { Authentication } from "@antelopejs/interface-auth";
import { Task } from "../db/tables/task";

@RegisterDataController()
@Authentication()
class TaskDataController extends DataController(
  Task,
  DefaultRoutes.All,
  Controller("/api/tasks")
) {}

Every route now requires a valid authentication token. Requests without authentication are rejected before reaching the route handler.

Mixed Access Patterns

Class-level @Authentication() is all-or-nothing. When you need a mix of public read endpoints and protected write endpoints on the same table, split the routes across two controllers mounted at the same base path — one protected, one public.

src/data-api/articles.ts
import { DataController, RegisterDataController, DefaultRoutes } from "@antelopejs/interface-data-api";
import { Controller } from "@antelopejs/interface-api";
import { Authentication } from "@antelopejs/interface-auth";
import { Article } from "../db/tables/article";

@RegisterDataController()
class PublicArticleController extends DataController(
  Article,
  {
    get: DefaultRoutes.Get,
    list: DefaultRoutes.List,
  },
  Controller("/api/articles")
) {}

@RegisterDataController()
@Authentication()
class ProtectedArticleController extends DataController(
  Article,
  {
    new: DefaultRoutes.New,
    edit: DefaultRoutes.Edit,
    delete: DefaultRoutes.Delete,
  },
  Controller("/api/articles")
) {}

Anyone can read articles, but only authenticated users can create, edit, or delete them.

Custom Route Options

The DefaultRoutes.WithOptions function accepts an optional third argument for the endpoint path. The third argument replaces the final path segment of a specific route.

DefaultRoutes.WithOptions(DefaultRoutes.Get, {}, "by-id")

With this override the Get route becomes GET /api/tasks/by-id?id=<id> instead of the default GET /api/tasks/get?id=<id>. The options object forwards query-parameter defaults to the route handler (for example { noPluck: true } or { noForeign: true }); it is not a place to plug in authentication. Use custom endpoints when the default URL segment does not match your API design or when you need to avoid conflicts with other controllers.

Combining with the Database Guide

The Data API works directly with tables defined using the database decorators. Here is a complete example that defines a table and exposes it as a CRUD API.

src/db/tables/task.ts
import { Table, RegisterTable, Index } from "@antelopejs/interface-database-decorators";

@RegisterTable("tasks", "main")
class Task extends Table {
  @Index()
  declare userId: string;

  declare title: string;
  declare description: string;
  declare completed: boolean;
}

export { Task };

The data controller below exposes CRUD endpoints for the Task table using DefaultRoutes.All:

src/data-api/tasks.ts
import { DataController, RegisterDataController, DefaultRoutes } from "@antelopejs/interface-data-api";
import { Controller } from "@antelopejs/interface-api";
import { Task } from "../db/tables/task";

@RegisterDataController()
class TaskDataController extends DataController(
  Task,
  DefaultRoutes.All,
  Controller("/api/tasks")
) {}

The Data API reads the table's schema and generates endpoints that match the table's structure. Column names become the fields in request and response bodies. You can use indexed fields for filtering in list operations.