Core ConceptsPrisma ORM

Routes

For every model in your Prisma schema, Arkos automatically generates a full set of RESTful endpoints. The model name is converted to kebab-case and pluralized for the route path.

Example: A UserProfile model is accessible at /api/user-profiles.

MethodEndpointDescriptionOperation
GET/api/[model]List recordsfindMany
POST/api/[model]Create one recordcreateOne
GET/api/[model]/:idGet one recordfindOne
PATCH/api/[model]/:idUpdate one recordupdateOne
DELETE/api/[model]/:idDelete one recorddeleteOne
POST/api/[model]/manyCreate multiple recordscreateMany
PATCH/api/[model]/manyUpdate multiple recordsupdateMany
DELETE/api/[model]/manyDelete multiple recordsdeleteMany

All routes are mounted under your configured globalPrefix (default: /api). See Configuration for more.

Configuring Model Routes

Every generated endpoint accepts the same configuration object used in ArkosRouter — disable routes, add rate limiting, authentication, and more via the Route Hook in your router file src/modules/<model>/<model>.router.ts where each key maps to the operation name from the table above:

src/modules/post/post.router.ts
import { ArkosRouter, RouterConfig, RouteHook } from "arkos";
import postPolicy from "@/src/modules/post/post.policy";
import UpdateUserSchema from "@/src/modules/post/schemas/post.schema";

export const hook: RouteHook = {
  findMany: { authentication: false },
  findOne: { authentication: true },
  createOne: { authentication: postPolicy.Create },
  updateOne: { validation: { body: UpdateUserSchema } },
  deleteOne: { disabled: true },
  createMany: { disabled: true },
  updateMany: { disabled: true },
  deleteMany: { disabled: true }
};

const router = ArkosRouter();

export default router;

RouteHook is the new name for export const config: RouterConfig introduced in v1.6. The old name still works but will log a deprecation warning. See Route Hook for the full guide.

See the full configuration object reference at ArkosRouter Configuration Object.

Intercepting Model Requests

Every generated endpoint can be intercepted — run logic before or after any operation without replacing the built-in behavior. For example, to log every time a post is created:

@/src/modules/post/post.interceptors.ts
import { ArkosRequest, ArkosResponse, ArkosNextFunction } from "arkos";

export const afterCreateOne = [
  async (req: ArkosRequest, res: ArkosResponse, next: ArkosNextFunction) => {
    console.log("Post created:", res.locals.data);
    next();
  },
];

See Prisma Model Interceptors for the full list of available hooks and what you can do with them.

Sending Model Requests

The examples below use a Post model. Requests that require authentication pass the token via the Authorization header — it can also be sent through cookies, which are set automatically on login depending on your configuration.

Find Many Posts

GET /api/posts
Authorization: Bearer YOUR_API_TOKEN

Response:

{
  "total": 42,
  "results": 10,
  "data": [
    {
      "id": "1",
      "title": "Getting Started with Arkos",
      "content": "...",
      "authorId": "123",
      "published": true,
      "createdAt": "2025-04-05T14:23:45.123Z",
      "updatedAt": "2025-04-05T14:23:45.123Z"
    }
  ]
}

Find One Post

GET /api/posts/1

Response:

{
  "data": {
    "id": "1",
    "title": "Getting Started with Arkos",
    "content": "...",
    "authorId": "123",
    "published": true,
    "createdAt": "2025-04-05T14:23:45.123Z",
    "updatedAt": "2025-04-05T14:23:45.123Z"
  }
}

Create One Post

POST /api/posts
Content-Type: application/json
Authorization: Bearer YOUR_API_TOKEN
{
  "title": "My New Post",
  "content": "Post content here",
  "authorId": "123",
  "published": false
}

Response:

{
  "data": {
    "id": "42",
    "title": "My New Post",
    "content": "Post content here",
    "authorId": "123",
    "published": false,
    "createdAt": "2025-04-05T14:23:45.123Z",
    "updatedAt": "2025-04-05T14:23:45.123Z"
  }
}

Create Multiple Posts

POST /api/posts/many
Content-Type: application/json
Authorization: Bearer YOUR_API_TOKEN
[
  { "title": "First Post", "content": "...", "authorId": "123" },
  { "title": "Second Post", "content": "...", "authorId": "123" }
]

Response:

{
  "total": 2,
  "results": 2,
  "data": [
    {
      "id": "43",
      "title": "First Post",
      "content": "...",
      "authorId": "123",
      "createdAt": "2025-04-05T14:23:45.123Z",
      "updatedAt": "2025-04-05T14:23:45.123Z"
    },
    {
      "id": "44",
      "title": "Second Post",
      "content": "...",
      "authorId": "123",
      "createdAt": "2025-04-05T14:23:45.123Z",
      "updatedAt": "2025-04-05T14:23:45.123Z"
    }
  ]
}

Update One Post

PATCH /api/posts/1
Content-Type: application/json
Authorization: Bearer YOUR_API_TOKEN
{
  "title": "Updated Title",
  "published": true
}

Response:

{
  "data": {
    "id": "1",
    "title": "Updated Title",
    "content": "...",
    "authorId": "123",
    "published": true,
    "createdAt": "2025-04-05T14:23:45.123Z",
    "updatedAt": "2025-04-05T15:30:12.456Z"
  }
}

Update Multiple Posts

Filters are passed as query parameters — all matched records are updated with the request body.

PATCH /api/posts/many?authorId=123&published=false
Content-Type: application/json
Authorization: Bearer YOUR_API_TOKEN
{
  "published": true
}

Response:

{
  "total": 5,
  "results": 5,
  "data": [
    {
      "id": "2",
      "title": "Draft Post",
      "published": true,
      "updatedAt": "2025-04-05T15:30:12.456Z"
    }
  ]
}

Delete One Post

DELETE /api/posts/1
Authorization: Bearer YOUR_API_TOKEN

Response: 204 No Content

Delete Multiple Posts

Filters are passed as query parameters — all matched records are deleted.

DELETE /api/posts/many?authorId=123&published=false
Authorization: Bearer YOUR_API_TOKEN

Response:

{
  "total": 3,
  "results": 3,
  "data": [
    { "id": "7", "title": "Deleted Draft Post" }
  ]
}

Bulk delete requires at least one filter query parameter. Sending DELETE /api/posts/many with no filters will return a 400 error to prevent accidental mass deletion.

Querying

All list endpoints (GET /api/[model]) support a rich set of query parameters that translate directly into Prisma queries.

Arkos supports two interchangeable syntaxes — use whichever feels natural, or mix them in the same request:

  • Bracket notation: price[gte]=50&price[lt]=100
  • Django-style: price__gte=50&price__lt=100

Filtering

Basic filtering matches records where any of the conditions are true (OR by default):

GET /api/posts?published=true&authorId=123

To switch to AND logic, use filterMode:

GET /api/posts?filterMode=AND&published=true&authorId=123

Comparison operators:

GET /api/products?price[gte]=50&price[lt]=100
# or
GET /api/products?price__gte=50&price__lt=100

Supported operators: gte, gt, lte, lt, contains, startsWith, endsWith, in, notIn, and all other Prisma filter operators.

Nested / relational filtering:

GET /api/posts?author[age]=30&comments[some][content][contains]=interesting
# or
GET /api/posts?author__age=30&comments__some__content__contains=interesting

Full-text search across all string fields except those ending in id, ids, ID, or IDs:

GET /api/posts?search=arkos

This generates a case-insensitive contains check across every eligible string field on the model.

Sorting

Prefix a field with - for descending order. Comma-separate multiple fields:

GET /api/posts?sort=-createdAt,title

This sorts by createdAt descending, then title ascending.

Pagination

GET /api/posts?page=2&limit=20

limit defaults to 30. The response always includes total (all matching records) and results (records returned in this page).

Field Selection

Select specific fields:

GET /api/posts?fields=id,title,published

Include a relation (adds to default fields):

GET /api/posts?fields=+author

Exclude specific fields:

GET /api/posts?fields=-content,-updatedAt

You can combine these in a single request:

GET /api/posts?fields=id,title,+author,-updatedAt

Combining Parameters

All query parameters work together:

GET /api/posts?search=arkos&published=true&sort=-createdAt&page=1&limit=10&fields=id,title,+author

Error Responses

// 404 - Record not found
{
  "code": "PostNotFound",
  "message": "Post with id '999' not found."
}

// 400 - Missing filters on bulk operation
{
  "code": "MissingFilterCriteria",
  "message": "Filter criteria not provided for bulk deletion."
}

For the full list of possible errors, see Error Handling.

Customizing Model Routes

  • Interceptors — Run logic before or after any generated endpoint
  • Authentication — Control which endpoints require authentication and which roles can access them
  • Validation — Add request body and query parameter validation