Core ConceptsComponents

Services

A service contains your business logic. Controllers call services, and services interact with the database or other external systems. Keeping this separation means your logic stays testable and reusable — the same service method can be called from an HTTP handler, a background job, or another service.

Creating a Service

If you are working with Prisma model routes, Arkos provides BaseService which already implements findMany, findOne, createOne, updateOne, deleteOne, createMany, updateMany, deleteMany, and count — the methods shown above are already built in. See BaseService for Prisma Models.

src/modules/post/post.service.ts
import postRepository from "./post.repository";

class PostService {
  async getFeatured() {
    return postRepository.findMany({ where: { featured: true } });
  }

  async publish(id: string) {
    return postRepository.update({
      where: { id },
      data: { publishedAt: new Date() },
    });
  }
}

const postService = new PostService();

export default postService;

BaseService for Prisma Models

When your service maps to a Prisma model, extend BaseService. It provides findMany, findOne, createOne, updateOne, deleteOne, createMany, updateMany, deleteMany, and count — all wired to Prisma with built-in support for Service Hooks.

src/modules/post/post.service.ts
import { BaseService } from "arkos/services";

class PostService extends BaseService<"post"> {}

const postService = new PostService("post");

export default postService;

Adding Custom Methods

Extend with your own methods alongside the built-in ones:

src/modules/post/post.service.ts
import { BaseService } from "arkos/services";
import { Prisma } from "@prisma/client";

class PostService extends BaseService<"post"> {
  async getFeatured() {
    return this.findMany({ where: { featured: true } });
  }

  async publish(id: string) {
    return this.updateOne(
      { where: { id } },
      { data: { publishedAt: new Date() } }
    );
  }

  async getStats() {
    const [total, published] = await Promise.all([
      this.count(),
      this.count({ where: { publishedAt: { not: null } } }),
    ]);
    return { total, published };
  }
}

const postService = new PostService("post");

export default postService;

Calling Services Programmatically

BaseService methods accept an optional context object as their last argument. This context is passed to Service Hooks and carries the authenticated user:

const post = await postService.createOne(
  { title: "Hello", body: "World" },
  { include: { author: true } },
  { user: req.user }
);

Skipping Hooks

When you need to bypass service hooks for a specific call:

await postService.deleteOne(
  { where: { id } },
  {},
  { skip: ["before", "after"] }
);

Scaffold with the CLI

arkos generate service --module post
arkos g s -m post