Skip to main content

Adding Custom Routers

Arkos provides a flexible routing system that lets you create custom API endpoints alongside the auto-generated Prisma model routes. Whether you need standalone endpoints for complex business logic or want to extend your model APIs with custom functionality, Arkos has you covered.

New in v1.4.0-beta: Arkos Router brings batteries-included features like validation, authentication, rate limiting, and OpenAPI documentation through declarative configuration. While Express Router still works for backward compatibility, we highly recommend using ArkosRouter.

Quick Start

// src/routers/analytics.router.ts
import { ArkosRouter } from "arkos";
import analyticsController from "../controllers/analytics.controller";

const analyticsRouter = ArkosRouter();

analyticsRouter.get(
{
path: "/api/analytics/dashboard",
authentication: {
action: "View",
resource: "dashboard",
rule: { roles: ["Admin", "Coordinator"] },
},
},
analyticsController.getDashboard
);

export default analyticsRouter;
// src/app.ts
import arkos from "arkos";
import analyticsRouter from "./routers/analytics.router";

arkos.init({
use: [analyticsRouter], // Register your routers
});

Understanding Your Options

Arkos offers two approaches for custom routing:

1. Custom Routers

Use when: Creating standalone endpoints that don't relate to a specific Prisma model

Examples:

  • Analytics dashboards
  • Complex operations spanning multiple models
  • Custom authentication flows
  • Feature-based APIs (search, exports, webhooks)

Location: Anywhere (typically src/routers/)

2. Customizing Prisma Model Routers

Use when: Extending or modifying auto-generated model endpoints

Examples:

  • Adding a "share" action to posts
  • Custom search for products
  • Disabling bulk operations
  • Overriding default behavior

Location: src/modules/{model-name}/{model-name}.router.ts

Decision Flow

Does your endpoint directly relate to a single Prisma model?

  • Yes → Customize the Prisma Model Router
  • No → Create a Custom Router

Custom Routers

Custom routers let you define entirely new API endpoints separate from your Prisma models.

Creating a Custom Router

// src/routers/reports.router.ts
import { ArkosRouter } from "arkos";
import z from "zod";
import reportsController from "../controllers/reports.controller";

const reportsRouter = ArkosRouter();

reportsRouter.get(
{ path: "/api/reports/summary" },
reportsController.getSummary
);

const GenerateReportSchema = z.object({
type: z.enum(["sales", "inventory", "customers"]),
startDate: z.string().datetime(),
endDate: z.string().datetime(),
});

reportsRouter.post(
{
path: "/api/reports/generate",
authentication: {
resource: "report",
action: "Generate",
rule: ["Admin", "Manager"],
},
validation: { body: GenerateReportSchema },
rateLimit: { windowMs: 60000, max: 10 },
},
reportsController.generateReport
);

export default reportsRouter;
Configuration Object

Notice the configuration object as the first argument. This declarative approach gives you access to validation, authentication, rate limiting, and more. See Arkos Router API Reference for all options.

Path Prefix

Custom routers are NOT automatically prefixed with /api. You must include the full path in your route definitions to maintain consistency with Arkos's auto-generated routes.

You can quickly generate a custom router file by using the built-in cli command.

npx arkos generate router --module router-name

Or shorthand

npx arkos g r -m router-name

This will automatically create an src/routers/router-name.router.{ts|js} file in your project, you can read more about router generation using the CLI at CLI Router Generation Guide.

info

Notice that when using the CLI to generate custom routers they will be generated by default at src/routers/{router-name}.router.{ts|js} which you can customize by passing the option -p for path when running the command

Registering Custom Routers

Add your router to the use array in your Arkos initialization:

// src/app.ts
import arkos from "arkos";
import reportsRouter from "./routers/reports.router";
import webhooksRouter from "./routers/webhooks.router";

arkos.init({
use: [reportsRouter, webhooksRouter],
});
Middleware Stack

Custom routers in the use array are added after all built-in Arkos routers. They will not overwrite any built-in routes.

Adding Features to Routes

ArkosRouter supports declarative configuration for common needs:

Authentication

// Simple authentication
router.get(
{
path: "/api/admin/dashboard",
authentication: true,
},
controller.getDashboard
);

// With role-based access control
router.post(
{
path: "/api/admin/settings",
authentication: {
resource: "settings",
action: "Update",
rule: ["Admin"],
},
},
controller.updateSettings
);

Learn more: Authentication System

Validation

import z from "zod";

const CreateUserSchema = z.object({
email: z.string().email(),
name: z.string().min(2),
role: z.enum(["User", "Admin"]),
});

router.post(
{
path: "/api/users",
validation: {
body: CreateUserSchema,
},
},
controller.createUser
);

Learn more: Request Data Validation

Rate Limiting

router.post(
{
path: "/api/reports/generate",
rateLimit: {
windowMs: 15 * 60 * 1000, // 15 minutes
max: 5, // 5 requests per window
message: "Too many report requests, try again later",
},
},
controller.generateReport
);

File Uploads

router.post(
{
path: "/api/upload/avatar",
authentication: true,
experimental: {
uploads: { type: "single", field: "avatar" },
},
},
controller.uploadAvatar
);

Learn more: File Upload Guide

OpenAPI Documentation

router.post(
{
path: "/api/reports/generate",
validation: {
body: GenerateReportSchema,
},
experimental: {
openapi: {
summary: "Generate custom report",
description: "Creates a report based on the provided parameters",
tags: ["Reports"],
responses: {
200: { description: "Report generated successfully" },
400: { description: "Invalid parameters" },
},
},
},
},
controller.generateReport
);

Learn more: OpenAPI/Swagger Guide

Other ArkosRouter Features

ArkosRouter supports many more features through declarative configuration:

  • Query Parsing: Automatic type conversion for query parameters
  • Compression: Per-route response compression
  • Custom Body Parsers: For webhooks and special content types
  • Error Handling: Automatic async error catching

See the complete Arkos Router API Reference for all available options.

Customizing Prisma Model Routers

Prisma model routers are auto-generated from your schema, but you can extend them with custom endpoints or modify their behavior. You can quickly generate a router file by using the built-in cli command:

npx arkos generate router --module user

Or shorthand

npx arkos g r -m user

This will automatically create an src/modules/user/user.router.{ts|js} file in your project, you can read more about router generation using the CLI at CLI Router Generation Guide.

Adding Custom Endpoints

To add custom endpoints to an existing model's API:

// src/modules/post/post.router.ts
import { ArkosRouter } from "arkos";
import { RouterConfig } from "arkos";
import z from "zod";
import postController from "./post.controller";

// Configuration for auto-generated endpoints
export const config: RouterConfig = {
// Leave empty if you're just adding endpoints
};

// Create router for custom endpoints
const router = ArkosRouter();

const SharePostSchema = z.object({
recipients: z.array(z.string().email()),
message: z.string().max(500).optional(),
});

// Add custom "share" endpoint → /api/posts/:id/share
router.post(
{
path: "/:id/share",
authentication: {
resource: "post",
action: "Share",
rule: ["User", "Admin"],
},
validation: {
body: SharePostSchema,
params: z.object({ id: z.string() }),
},
},
postController.sharePost
);

// Add custom "featured" endpoint → /api/posts/featured
router.get(
{
path: "/featured",
rateLimit: { windowMs: 60000, max: 50 },
},
postController.getFeaturedPosts
);

export default router;
Path Resolution

Paths are automatically prefixed with the model's base path (/api/posts). Just specify the part after the model name (/:id/share, not /api/posts/:id/share).

Naming Conventions
  • Export configuration as config (lowercase)
  • Export router as the default export

If these conventions aren't followed, Arkos won't recognize your customizations.

Configuring Auto-Generated Endpoints

You can configure individual auto-generated endpoints with all ArkosRouter features:

// src/modules/product/product.router.ts
import { ArkosRouter } from "arkos";
import { RouterConfig } from "arkos";

export const config: RouterConfig = {
// Configure the findMany endpoint
findMany: {
authentication: false, // Public
rateLimit: {
windowMs: 60000,
max: 100,
},
},

// Configure createOne endpoint
createOne: {
authentication: {
resource: "product",
action: "Create",
rule: ["Admin", "Manager"],
},
experimental: {
uploads: { type: "array", field: "images", maxCount: 8 },
},
},

// Configure deleteOne endpoint
deleteOne: {
authentication: {
resource: "product",
action: "Delete",
rule: ["Admin"],
},
rateLimit: {
windowMs: 60000,
max: 5,
},
},
};

const productRouter = ArkosRouter();

export default productRouter;

Disabling Auto-Generated Endpoints

// src/modules/post/post.router.ts
import { ArkosRouter } from "arkos";
import { RouterConfig } from "arkos";

export const config: RouterConfig = {
// Disable specific endpoints
createMany: {
disabled: true,
},
deleteMany: {
disabled: true,
},
updateMany: {
disabled: true,
},
};

export default ArkosRouter();
Preferred Syntax

While disable: true and disable: { createMany: true } still work for backward compatibility, we recommend the new syntax (createMany: { disabled: true }) for consistency.

When all endpoints are disabled, Arkos will not generate:

  • POST /api/posts
  • GET /api/posts/:id
  • PATCH /api/posts/:id
  • DELETE /api/posts/:id
  • POST /api/posts/many
  • GET /api/posts
  • PATCH /api/posts/many
  • DELETE /api/posts/many

Overriding Auto-Generated Endpoints

To completely replace an auto-generated endpoint:

// src/modules/post/post.router.ts
import { ArkosRouter, RouterConfig } from "arkos";
import z from "zod";
import { prisma } from "../../utils/prisma";
import postController from "./post.controller";

export const config: RouterConfig = {
findMany: {
disabled: true, // Disable the endpoint you're overriding
},
};

const router = ArkosRouter();

const PostQuerySchema = z.object({
published: z.boolean().optional(),
authorId: z.string().optional(),
tag: z.string().optional(),
});

// Override GET /api/posts
router.get(
{
path: "/",
authentication: true,
validation: { query: PostQuerySchema },
rateLimit: { windowMs: 60000, max: 100 },
experimental: {
uploads: { type: "single", field: "thumbnail", maxCount: 8 },
},
},
postController.myOwnMethod
);

export default router;
Important

When overriding endpoints, you must manually implement features like validation, authentication, and error handling. The example above shows how to do this using ArkosRouter's configuration options.

Comparison

FeatureCustom RoutersCustomizing Prisma Model Routers
PurposeCreate entirely new endpointsExtend or modify existing model endpoints
Path BaseYou define the full pathBased on the model name (e.g., /api/products)
RegistrationAdded to use array (v1.4) or routers.additional (v1.3)Auto-detected based on file location
File LocationAnywhere (typically src/routers)Must be in src/modules/{model-name}/{model-name}.router.ts
Built-in FeaturesAll Arkos Router features availableAll ArkosRouter features available
Auto-generated EndpointsNoneCan configure, disable, or override
Configuration ExportNot requiredMust export config and default router

Middleware Order

Understanding middleware execution order helps when debugging:

  1. Built-in Arkos middlewares (body parser, CORS, etc.)
  2. Auto-generated Prisma model routers
  3. Custom routers from the use array (v1.4) or routers.additional (v1.3)
  4. Route-specific middlewares (defined in ArkosRouter config or Express chains)

Custom routers cannot override built-in routes because they're registered later in the stack.

Now that you understand custom routing, explore related topics: