Your First CAP Project
Scaffold a CAP project with TypeScript, define our first entity and service, and meet `cds watch`.
Scaffolding the Project
Let's create our project. Open a terminal and run:
cds init cap-event-management --add nodejs,typescriptcds init scaffolds a new CAP project. The --add nodejs flag adds the CAP Node.js runtime, and --add typescript layers on TypeScript support — tsconfig.json, type declarations, and cds-typer.
Now let's open it:
cd cap-event-management
code .Project Structure
Here's what cds init generated:
cap-event-management/
├── app/ # UI applications (Fiori Elements, etc.)
├── db/ # Domain models and data (CDS schema files)
├── srv/ # Service definitions and handler code
├── package.json # Dependencies and CDS configuration
└── tsconfig.json # TypeScript compiler configurationThree folders, each with a clear responsibility:
db/— Our domain model lives here. CDS entity definitions and aspects. Think of this as "what does my data look like?" (Seed data goes intest/data/— we'll set that up in lesson 07.)srv/— Our services and business logic. CDS service definitions and TypeScript handler files. Think of this as "what can consumers do with my data?"app/— Our UI layer. This stays empty for now — we'll add a Fiori Elements app in Section 4.
This separation is a CAP convention, not a strict requirement. But we will stick with it, since every CAP project follows this layout, and tooling (like cds build) relies on it.
package.json & CDS Configuration
Open package.json:
{
"name": "cap-event-management",
"version": "1.0.0",
"dependencies": {
"@sap/cds": "^9"
},
A few things to note:
@sap/cds— The CAP runtime. This is the framework itself.@cap-js/sqlite— SQLite database plugin for local development. Our data will live in-memory (or a local file) during development — no database server needed.@cap-js/cds-types— TypeScript declarations for@sap/cdsAPIs. This is what gives us autocomplete when using CAP's Node.js APIs directly.tsx— A fast TypeScript executor.cds watchuses it under the hood to run our handlers without a separate compile step.@cap-js/cds-typer— The type generator we discussed in the previous lesson. It creates TypeScript interfaces from our CDS models.importssection — A Node.js subpath import that maps#cds-models/*to the generated types at runtime. TypeScript uses a matchingpathsentry intsconfig.jsonfor the same purpose at compile time — we'll see that next.
Install the dependencies:
npm installtsconfig.json
The generated tsconfig.json is already configured for CAP:
{
"compilerOptions": {
"target": "ESNext",
"module": "NodeNext",
"moduleResolution": "NodeNext",
Two paths entries worth noting:
@sap/cds→@cap-js/cds-types— When we writeimport cds from '@sap/cds', TypeScript resolves the types from@cap-js/cds-typesinstead of the runtime package. This gives us proper type checking and autocomplete for all CAP APIs.#cds-models/*→@cds-models/*— Points to the types generated bycds-typer. When we writeimport { Events } from '#cds-models/EventService', TypeScript resolves it to the generated interfaces. (At runtime, theimportsfield inpackage.jsonhandles the same mapping — they work as a pair.)
We'll see both of these in action shortly.
Our First Entity
Let's create our first database entity.
Create
db/schema.cds:
namespace events;
entity Events {
key ID : UUID;
title : String(100);
startDate : DateTime;
endDate : DateTime;
}That's a CDS entity definition. If you've worked with SQL, it looks familiar — but it's more concise. A few things to note:
namespace events— Keeps our entities organized. The full name of this entity becomesevents.Events.key ID : UUID— A primary key with auto-generated UUID values.- String, DateTime — CDS built-in types that map to appropriate database column types. We'll cover these in detail in the next lesson.
Our First Service
An entity on its own doesn't do much — it needs to be exposed through a service.
Create
srv/event-service.cds:
using { events } from '../db/schema';
service EventService {
entity Events as projection on events.Events;
}Two lines. This creates an OData V4 service called EventService that exposes the Events entity. The as projection on syntax means "expose this entity as is through the service" — CAP generates all CRUD operations automatically.
Starting the Development Server
Now let's see it run:
cds watchThis is the command you'll use the most throughout this course. cds watch does several things:
- Compiles your CDS models
- Generates TypeScript types via
cds-typer - Deploys the schema to an in-memory SQLite database
- Starts the server (default:
http://localhost:4004) - Watches for file changes and restarts automatically
You should see output like:
[cds] - loaded model from 2 file(s):
db/schema.cds
srv/event-service.cds
[cds] - connect to db > sqlite { url: ':memory:' }
> init from db/schema.cds
[cds] - serving EventService { path: '/odata/v4/event' }
[cds] - server listening on { url: 'http://localhost:4004' }Open http://localhost:4004 in your browser. We'll see the CAP welcome page listing our service. Click on EventService and we'll see the OData service document. Click on Events and we'll get... an empty array. That's expected — we haven't added any data yet.
But look at what we already have. Try these URLs:
http://localhost:4004/odata/v4/event/Events— the entity sethttp://localhost:4004/odata/v4/event/$metadata— the full OData metadata document
That metadata document describes our entire API: entities, properties, types, navigation properties, and available operations. This is what Fiori Elements will read later to auto-generate a UI. And we didn't write any of it.
cds-typer in Action
When cds watch started, it ran cds-typer automatically. Look at our project — there's a new @cds-models/ folder:
@cds-models/
├── EventService/
│ └── index.ts
├── events/
│ └── index.ts
└── index.tsOpen @cds-models/events/index.ts. We'll see a generated TypeScript interface for our Events entity with all the fields and their types. This is will give us type safety in our handler code.
Testing Our Service
Let's verify it works. We'll use the REST Client extension for VS Code — install it from the Extensions panel (search for "REST Client" by Huachao Mao). It lets us send HTTP requests directly from .http files, and we can keep these files in our repo as living API documentation.
Create
tests/requests.http:
### Read all events
GET http://localhost:4004/odata/v4/event/Events
### Create an event
POST http://localhost:4004/odata/v4/event/Events
Content-Type: application/json
{
"title": "SAP TechEd 2026",
"startDate":
Click "Send Request" above any ### block to execute it. Try the POST first to create an event, then the GET requests to see OData's filtering, sorting, and pagination in action.
None of these query capabilities required any code. CAP handles it all from the CDS model.