TypeScript patterns

How a CAP service is arranged once it is written in TypeScript — where handlers live, what registers them, and how business logic is kept apart from persistence. Distinct from typing, which is about the model being visible to the compiler; this is about the shape of the code around it.

Overview

SAP SHIPS

The shape capire gives a TypeScript service is the JavaScript one: custom logic is a subclass of cds.ApplicationService, its handlers are registered in init() with on, before and after, and the runtime finds the implementation in a file named after the service definition or in a lib/ or handlers/ subfolder beside it. The toolchain around that is official too — cds watch detects TypeScript from a tsconfig.json and runs cds-tsx, and a cds build task shipped with @cap-js/cds-typer precompiles to JavaScript for production.

Not covered: At the service's own implementation class. None of the pages read here divides a service's handlers across several classes, gives a draft's own events a place apart from the active entity's, or names a layer between a handler and the database — so a project that wants a class per entity, or business logic kept apart from persistence, writes that arrangement itself.

No page read for this assessment states SAP's intent about the arrangement inside srv, so the relationship between the two sides is unassessed.

SourceCore Services (Node.js) — capirechecked
THE COMMUNITY ADDS

Different ways of deciding what makes a method a handler, which is the choice a reader faces here: a decorator on the class or the method, as in cds-ts-dispatcher, a method name matched by convention, as in cap-handler-framework, or a folder layout that fixes where an entity's class goes, as in cds-plugin-handlers. Beside them sits the persistence half, such as cds-ts-repository: the queries called through a class with methods instead of written as CQN inside the handler.

Artifacts8

8 of 8 artifacts
ArtifactSignals
Build tools1
cds-plugin-typescriptCDS build task compiling TypeScript sources during cds build
Signals
none
CDS plugins2
cap-handler-frameworkConvention-based handler layer for CAP with draft lifecycle hooks
Signals
none
cds-plugin-handlersOne handler file per entity in CAP services
Signals
none
CLI tools1
cds2typesCLI generating TypeScript interfaces from a CDS model
Signals
none
Packages4
@dxfrontier/cds-ts-dispatcherDecorator-driven handler registration for CAP in TypeScript
Signals
none
@dxfrontier/cds-ts-repositoryTyped BaseRepository over CAP entities
Signals
none
cds-hyper-app-serviceTyped class-based service layer over CAP ApplicationService
Signals
Functional boundary
cds-routing-handlersDecorator-based routing of CAP service handlers in TypeScript
Signals
Quiet

Tasks3Beta

CoversCovers, under conditionsAssessed, not knownNot assessed
Handle a draft's own events apart from the active entity's1 covers, 1 under conditions, 1 not known

A draft-enabled entity's own events — NEW, PATCH, EDIT, SAVE and DISCARD — are handled in places of their own in a CAP service written in TypeScript, apart from the CREATE, UPDATE and DELETE handlers of the active entity.

Covers where a draft's events are handled and what gets a method there. The draft lifecycle itself, the drafts entity, the locking and the activation, is the runtime's and is draft-handling, not this outcome.

Coverscap-handler-frameworkGives the draft events hooks of their own, named apart from the active-entity ones, and its README states the separation as the point: explicit hooks for NEW, PATCH, EDIT, SAVE and DISCARD, kept away from the active entity's hooks.

Remaining work: A handler class states that it handles drafts before the draft hooks are mapped, and what each hook does is the application's code. Which hook registers on the drafts entity and which on the active one follows the runtime's own rule.

Open artifact →

Covers, under conditions@dxfrontier/cds-ts-dispatcherCarries a second set of decorators for the draft version of an entity's events beside the active-entity ones, so a method is marked as a draft handler where it is written rather than registered against a drafts entity by hand.

Conditions: The entity is annotated @odata.draft.enabled: true

Remaining work: The method bodies are the application's. The draft decorators are offered for an entity whose model says drafts are enabled; the README frames them that way rather than as a general set.

Open artifact →

Assessed, not knowncds-routing-handlersThe decorator reference read today covers the four CRUD events, actions and functions, and names no draft event. Nothing in it rules a draft handler out either, so what happens for a draft-enabled entity is not established.

Remaining work: Not established from the README. A project would have to find out whether a draft event reaches an annotated method at all.

Open artifact →

Keep data access out of a CAP service's handlers3 covers

The data access of a CAP service written in TypeScript is called through classes with methods rather than written as queries inside the event handlers, so a handler holds the business logic and the queries sit behind one object.

Covers where the queries live and what calls them. Which database is underneath, the transaction they run in, and whether the entity types are generated are not this outcome.

Covers@dxfrontier/cds-ts-dispatcherNames the layer and puts the object in the handler's hands: its documentation recommends Controller-Service-Repository, gives the persistence layer the @Repository class decorator, and @Inject places a repository or a service class into a field of another class.

Remaining work: The repository class itself is the application's, or the matching package. The decorators place the object; what it does for a given entity is written either way.

Open artifact →

Covers@dxfrontier/cds-ts-repositoryA class extends BaseRepository for one entity and the reads and writes are called as its methods, so the handler calls a method instead of writing a query. Its documentation sets out both ways of reaching it, in a standard CAP TypeScript project and injected into a dispatcher's handler class, and BaseRepositoryDraft extends the same class to the draft entity.

Remaining work: Which methods a given entity needs beyond the ones the base class provides, and the queries behind them, are the application's own. The entity types the class is typed on are generated separately.

Open artifact →

Coverscds-plugin-handlersAnswers the outcome for data that is not the project's own entities: a services folder holds a class per external service, and the requests to an external API or another database are written there rather than in the handler.

Remaining work: The project's own entities are not covered by that layer. What the README states is where the external-service classes go; the calls inside them are the application's.

Open artifact →

Split one CAP service implementation into a class per entity3 covers, 1 under conditions, 1 not known

The custom logic of a CAP service written in TypeScript sits in one class per entity rather than in a single service implementation file, and each class's methods still reach the framework as the before, on and after handlers for that entity.

Covers how one service's handlers are divided up and what makes a method a handler. What the handlers then do, the generated types they use, which is typing, and how the project is transpiled or built are not this outcome.

Covers@dxfrontier/cds-ts-dispatcherA class decorated with @EntityHandler is the place one entity's handlers go, and the method decorators beside it, @BeforeCreate, @BeforeRead and the rest, register what srv.before and srv.on would have. A CDSDispatcher instance in the service implementation is given the handler classes.

Remaining work: The service implementation keeps one place that lists the handler classes, the CDSDispatcher constructor, so a new class is added there as well as written. The decorator compiler options and the generated entity types the decorators take are the project's own setup.

Open artifact →

Coverscap-handler-frameworkA handler class is matched by the names of its methods, beforeCreate, onRead, afterUpdate, with nothing annotated. The CDS plugin generates the handlers/index.ts that the service's registerHandlers call reads, so a new handler file is picked up without an edit elsewhere.

Remaining work: The service implementation still calls registerHandlers in its init(), and the entity a class belongs to is returned by a method on the class rather than declared to the framework.

Open artifact →

Coverscds-plugin-handlersWhere a class goes is the declaration: a folder per service under src/handlers, and inside it a class per entity named after that entity. As a cds-plugin it loads itself, so the service implementation holds nothing that lists the classes.

Remaining work: The folder and file names carry the binding, so renaming a service or an entity in the model means moving or renaming files. The README read today does not state which method names a handler class uses.

Open artifact →

Covers, under conditionscds-routing-handlers@Handler marks a class for an entity and its annotated methods are registered as that entity's actions, the same mechanism the dispatcher uses and the earliest of these to offer it.

Conditions: reflect-metadata is installed and imported before the package is used emitDecoratorMetadata and experimentalDecorators are set in the project's tsconfig.json

Remaining work: The TypeScript decorator setup is the project's: reflect-metadata imported before anything else, and emitDecoratorMetadata and experimentalDecorators set in tsconfig.json. The README names no @sap/cds version it was built against.

Open artifact →

Assessed, not knowncds-hyper-app-serviceThe entry describes a typed layer over ApplicationService for writing handlers as class members, which is this outcome, but nothing retrieved for this assessment states how a member becomes a handler. The package declares no repository and its npm page answered 403 when it was requested on 2026-10-15.

Remaining work: Not established. A reader would have to read the published package to find out what it binds and how.

Open artifact →

Content10

Group by10 of 10 pieces
Piece, what it offers
How to use Cloud SDK for AI in an existing CAP NodeJS Project in CommonJS
SAP Community, Jacky_Liu1, Blog
Quick Implementation of DI/DI Container on SAP CAP: Node.js Hands-on Part
SAP Community, Umezuki, BlogThe reasoningRunnable code
How we could potentially have nestjs as runtime for SAP CAP
SAP Community, alex_myakinkii, Blog
Show 7 more
Use TypeScript with the SAP Cloud Application Programming Model
SAP Community, KevinR, BlogThe reasoningA recipeRunnable code
SAP CAP: Controller - Service - Repository architecture
SAP Community, Daniel Dragolea, BlogThe reasoningRunnable code
New majors
CDS-TS-Repository: Simplify SAP CAP Entity persistance with BaseRepository
SAP Community, Daniel Dragolea, BlogThe reasoningRunnable code
New majors
CDS-TS-Dispatcher: Simplifying SAP CAP TypeScript Development
SAP Community, Daniel Dragolea, BlogThe reasoningRunnable code
New majors
CAP Typescript + Fiori Typescript + live reload Initial app setup
SAP Community, Christophe_SW, BlogThe reasoningRunnable code
Core changed
Full Stack TypeScript App for Cloud Foundry - Sample Repository
SAP Community, former_member66, BlogThe reasoningA recipeRunnable code
Core changed
Taking CAP to the next level - with TypeScript
SAP Community, mrbandler, BlogThe reasoningRunnable code
Gone quiet
SAP CAP: Controller - Service - Repository architecture
SAP Community, Daniel Dragolea, BlogThe reasoningRunnable code
New majors
CDS-TS-Dispatcher: Simplifying SAP CAP TypeScript Development
SAP Community, Daniel Dragolea, BlogThe reasoningRunnable code
New majors
SAP CAP: Controller - Service - Repository architecture
SAP Community, Daniel Dragolea, BlogThe reasoningRunnable code
New majors
CDS-TS-Repository: Simplify SAP CAP Entity persistance with BaseRepository
SAP Community, Daniel Dragolea, BlogThe reasoningRunnable code
New majors
Taking CAP to the next level - with TypeScript
SAP Community, mrbandler, BlogThe reasoningRunnable code
Gone quiet
Taking CAP to the next level - with TypeScript
SAP Community, mrbandler, BlogThe reasoningRunnable code
Gone quiet

Seen working1

ArtifactSignals
Samples1
cap-ui5-gpt-chatChat app on CAP and SAPUI5 against the OpenAI API
Functional boundary

Details

What changed — and what it makes stale2
@sap/cds-dk, December 2024, CAP

cds watch detects TypeScript mode from a tsconfig.json and runs cds-tsx itself, so a TypeScript project is started with the same command as a JavaScript one. Until then the dev loop needed its own command.

Makes stale:

Setup steps that tell a reader to run cds-tsx watch or cds-ts watch to start a TypeScript project. The release note's own sentence is that this is not necessary anymore.

@cap-js/cds-typer 0.5.0, all scopes

Before this, deriving TypeScript types from a CDS model meant a separate community CLI run as an extra build step — cds2types was the one most people reached for, alongside hand-written declarations. In July 2023 cds-typer went open source under the cap-js GitHub org and onto npm as @cap-js/cds-typer, already supporting the array-of/many syntax and enums by the time of that announcement, giving the framework a maintained, official generator for the first time.

Makes stale:

Setup guidance from 2020-2022 that reaches for cds2types, hand-written .d.ts files or another community fork to get typed entities out of a CDS model. That gap is what cds-typer closed in mid-2023; what is left open, and what this capability records, is the shape of the code around the generated types — dispatchers, repositories, controller-service-repository layering — which the core still has no opinion on.

SourceJuly 2023 — sap/cds — capirechecked
Coverage
statecore and community, CAP
assessed
relationshipunassessed
Comparison properties4

These are the questions used to compare what the artifacts do. Values come from their documentation and may apply to a particular ecosystem or runtime. Each recorded value in the comparison opens its statement and sources.

How handlers bindLayers it namesDependency injectionDraft events
For your agent
Ref
capability:typescript-patterns
JSON
/capabilities/typescript-patterns.json
MCP
Connecting an agent