# Build your first dcupl application

In this tutorial, you will build a complete dcupl application from scratch. By the end, you will have a working app that loads data, queries it, and displays results.

**Time required:** 15 minutes

**What you will build:** A simple book library app that filters books by genre and displays results.

## Prerequisites

Before starting, make sure you have:

- A supported Node.js version, see [Compatibility](/sdk/compatibility#nodejs) (`node -v` to check)
- A code editor (VS Code recommended)
- Basic familiarity with TypeScript/JavaScript

This tutorial expands the five steps of the [Quick start](/sdk/quick-start) (create an instance, define a model, load data, initialize, query) into a small application. If you haven't run the Quick Start yet, do that first: it takes 5 minutes.

## Step 1: create a new project

Create a new directory for your project, initialize it and install the dcupl core package (for yarn or pnpm, see the [Quick start](/sdk/quick-start#install)):

```bash filename="terminal"
mkdir my-dcupl-app
cd my-dcupl-app
npm init -y
npm install @dcupl/core
```

TypeScript types are included, so no additional packages are needed.

## Step 2: create the application file

Create a new file called `app.ts` in your project root:

```typescript filename="app.ts"
import { Dcupl } from '@dcupl/core';

async function main() {
  console.log('Starting dcupl app...');

  // We will add code here in the next steps
}

main();
```

Run it to make sure everything is set up correctly:

```bash filename="terminal"
npx tsx app.ts
```

The first time, npx asks to download `tsx` (answer `y`), or install it in the project with `npm install -D tsx`. You should see "Starting dcupl app..." in your terminal.

## Step 3: create a dcupl instance

The `Dcupl` class is the entry point for everything. Create an instance at the top of your `main` function:

```typescript filename="app.ts"
import { Dcupl } from '@dcupl/core';

async function main() {
  // Create the dcupl instance
  const dcupl = new Dcupl();

  console.log('dcupl instance created');
}

main();
```

The dcupl instance is an empty container. Next, you will tell it what kind of data to expect.

## Step 4: define a model

Models describe the structure of your data. Think of them like TypeScript interfaces or database schemas.

Add this model definition after creating the dcupl instance:

```typescript filename="app.ts"
import { Dcupl } from '@dcupl/core';

async function main() {
  const dcupl = new Dcupl();

  // Define the Book model
  dcupl.models.set({
    key: 'Book',
    properties: [
      { key: 'title', type: 'string' },
      { key: 'author', type: 'string' },
      { key: 'genre', type: 'string' },
      { key: 'year', type: 'int' },
      { key: 'rating', type: 'float' },
    ],
  });

  console.log('Model defined');
}

main();
```

This tells dcupl that "Book" items have five properties: title, author, genre, year, and rating.

## Step 5: load data

Now add some data that matches the model. In a real app, this could come from an API or file. For now, use inline data:

```typescript filename="app.ts"
import { Dcupl } from '@dcupl/core';

async function main() {
  const dcupl = new Dcupl();

  // Define the Book model
  dcupl.models.set({
    key: 'Book',
    properties: [
      { key: 'title', type: 'string' },
      { key: 'author', type: 'string' },
      { key: 'genre', type: 'string' },
      { key: 'year', type: 'int' },
      { key: 'rating', type: 'float' },
    ],
  });

  // Load book data
  dcupl.data.set(
    [
      {
        key: 'b1',
        title: 'The Great Gatsby',
        author: 'F. Scott Fitzgerald',
        genre: 'Fiction',
        year: 1925,
        rating: 4.5,
      },
      {
        key: 'b2',
        title: 'To Kill a Mockingbird',
        author: 'Harper Lee',
        genre: 'Fiction',
        year: 1960,
        rating: 4.8,
      },
      {
        key: 'b3',
        title: 'A Brief History of Time',
        author: 'Stephen Hawking',
        genre: 'Science',
        year: 1988,
        rating: 4.6,
      },
      {
        key: 'b4',
        title: 'The Selfish Gene',
        author: 'Richard Dawkins',
        genre: 'Science',
        year: 1976,
        rating: 4.3,
      },
      {
        key: 'b5',
        title: '1984',
        author: 'George Orwell',
        genre: 'Fiction',
        year: 1949,
        rating: 4.7,
      },
      {
        key: 'b6',
        title: 'Cosmos',
        author: 'Carl Sagan',
        genre: 'Science',
        year: 1980,
        rating: 4.9,
      },
    ],
    { model: 'Book' },
  );

  console.log('Data loaded');
}

main();
```

Each item needs a unique `key` property. This is how dcupl identifies items internally.

## Step 6: initialize dcupl

Before querying, you must call `init()`. It processes your models and data and makes them queryable:

```typescript filename="app.ts"
// ... previous code ...

  // Initialize dcupl (processes models and data)
  await dcupl.init();

  console.log('dcupl initialized and ready');
}

main();
```

Run the app again to verify everything works:

```bash filename="terminal"
npx tsx app.ts
```

## Step 7: create your first query

Now for the fun part. Create a list to query your data. A list is a queryable view of one model. It remembers the conditions you add until you call `clear()`: `catalog.query` holds filtering and results, `catalog.fn` holds analysis functions like facets.

Also add a `Book` type below the import: passed to `dcupl.lists.create<Book>()`, it types the items you read from the list, so your editor knows that `book.title` is a string.

```typescript filename="app.ts"
import { Dcupl } from '@dcupl/core';

// The item type of the list's results
type Book = {
  key: string;
  title: string;
  author: string;
  genre: string;
  year: number;
  rating: number;
};

async function main() {
  const dcupl = new Dcupl();

  dcupl.models.set({
    key: 'Book',
    properties: [
      { key: 'title', type: 'string' },
      { key: 'author', type: 'string' },
      { key: 'genre', type: 'string' },
      { key: 'year', type: 'int' },
      { key: 'rating', type: 'float' },
    ],
  });

  dcupl.data.set(
    [
      {
        key: 'b1',
        title: 'The Great Gatsby',
        author: 'F. Scott Fitzgerald',
        genre: 'Fiction',
        year: 1925,
        rating: 4.5,
      },
      {
        key: 'b2',
        title: 'To Kill a Mockingbird',
        author: 'Harper Lee',
        genre: 'Fiction',
        year: 1960,
        rating: 4.8,
      },
      {
        key: 'b3',
        title: 'A Brief History of Time',
        author: 'Stephen Hawking',
        genre: 'Science',
        year: 1988,
        rating: 4.6,
      },
      {
        key: 'b4',
        title: 'The Selfish Gene',
        author: 'Richard Dawkins',
        genre: 'Science',
        year: 1976,
        rating: 4.3,
      },
      {
        key: 'b5',
        title: '1984',
        author: 'George Orwell',
        genre: 'Fiction',
        year: 1949,
        rating: 4.7,
      },
      {
        key: 'b6',
        title: 'Cosmos',
        author: 'Carl Sagan',
        genre: 'Science',
        year: 1980,
        rating: 4.9,
      },
    ],
    { model: 'Book' },
  );

  await dcupl.init();

  // Create a list for the Book model
  const bookList = dcupl.lists.create<Book>({ modelKey: 'Book' });

  // Get all books
  const allBooks = bookList.catalog.query.items();
  console.log('All books:', allBooks.length);

  // Get the first book
  const firstBook = bookList.catalog.query.first();
  console.log('First book:', firstBook?.title);
}

main();
```

Run it:

```bash filename="terminal"
npx tsx app.ts
```

You should see:

```
All books: 6
First book: The Great Gatsby
```

## Step 8: add filters

Filter the list to show only science books:

```typescript filename="app.ts"
// ... at the end of main(), after the Step 7 code ...

// Filter by genre
bookList.catalog.query.addCondition({
  attribute: 'genre',
  operator: 'eq',
  value: 'Science',
});

const scienceBooks = bookList.catalog.query.items();
console.log('\nScience books:');
scienceBooks.forEach((book) => {
  console.log(`  - ${book.title} by ${book.author}`);
});
```

Output:

```
Science books:
  - A Brief History of Time by Stephen Hawking
  - The Selfish Gene by Richard Dawkins
  - Cosmos by Carl Sagan
```

## Step 9: add multiple filters

Add another filter to find highly-rated science books:

```typescript filename="app.ts"
// Add a second condition (rating >= 4.5)
bookList.catalog.query.addCondition({
  attribute: 'rating',
  operator: 'gte',
  value: 4.5,
});

const topScienceBooks = bookList.catalog.query.items();
console.log('\nTop-rated science books (4.5+):');
topScienceBooks.forEach((book) => {
  console.log(`  - ${book.title} (${book.rating})`);
});
```

Output:

```
Top-rated science books (4.5+):
  - A Brief History of Time (4.6)
  - Cosmos (4.9)
```

Conditions on different attributes (here `genre` and `rating`) are combined with AND. Conditions on the same attribute are combined with OR, see [Query logic](/sdk/queries/query-logic).

## Step 10: sort and paginate

Sort results and limit the output. Add the sorting code at the end of `main()`:

```typescript filename="app.ts"
// Clear previous filters
bookList.catalog.query.clear();

// Get all books sorted by year (newest first)
const sortedBooks = bookList.catalog.query.items({
  sort: { attributes: ['year'], order: ['DESC'] },
});

console.log('\nBooks by year (newest first):');
sortedBooks.forEach((book) => {
  console.log(`  - ${book.year}: ${book.title}`);
});

// Paginate: get the first 3 books in the same order
const firstPage = bookList.catalog.query.items({
  sort: { attributes: ['year'], order: ['DESC'] },
  start: 0,
  count: 3,
});

console.log('\nFirst page (3 books):');
firstPage.forEach((book) => {
  console.log(`  - ${book.title}`);
});
```

Output:

```
Books by year (newest first):
  - 1988: A Brief History of Time
  - 1980: Cosmos
  - 1976: The Selfish Gene
  - 1960: To Kill a Mockingbird
  - 1949: 1984
  - 1925: The Great Gatsby

First page (3 books):
  - A Brief History of Time
  - Cosmos
  - The Selfish Gene
```

Options passed to `items()` apply only to that call, so the paging call passes `sort` again together with `start` and `count`.

## Step 11: get facet counts

Facets tell you how many items exist for each value. This is useful for building filter UIs:

```typescript filename="app.ts"
// Get facet counts for genres
const genreFacets = bookList.catalog.fn.facets({ attribute: 'genre' });

console.log('\nBooks by genre:');
genreFacets.forEach((facet) => {
  console.log(`  - ${facet.value}: ${facet.size} books`);
});
```

Output:

```
Books by genre:
  - Fiction: 3 books
  - Science: 3 books
```

## Complete code

Here is the complete application, Steps 1 to 11 in one file. Its output is the output of Steps 7 to 11:

```typescript filename="app.ts"
import { Dcupl } from '@dcupl/core';

// The item type of the list's results (Step 7)
type Book = {
  key: string;
  title: string;
  author: string;
  genre: string;
  year: number;
  rating: number;
};

async function main() {
  const dcupl = new Dcupl();

  dcupl.models.set({
    key: 'Book',
    properties: [
      { key: 'title', type: 'string' },
      { key: 'author', type: 'string' },
      { key: 'genre', type: 'string' },
      { key: 'year', type: 'int' },
      { key: 'rating', type: 'float' },
    ],
  });

  dcupl.data.set(
    [
      {
        key: 'b1',
        title: 'The Great Gatsby',
        author: 'F. Scott Fitzgerald',
        genre: 'Fiction',
        year: 1925,
        rating: 4.5,
      },
      {
        key: 'b2',
        title: 'To Kill a Mockingbird',
        author: 'Harper Lee',
        genre: 'Fiction',
        year: 1960,
        rating: 4.8,
      },
      {
        key: 'b3',
        title: 'A Brief History of Time',
        author: 'Stephen Hawking',
        genre: 'Science',
        year: 1988,
        rating: 4.6,
      },
      {
        key: 'b4',
        title: 'The Selfish Gene',
        author: 'Richard Dawkins',
        genre: 'Science',
        year: 1976,
        rating: 4.3,
      },
      {
        key: 'b5',
        title: '1984',
        author: 'George Orwell',
        genre: 'Fiction',
        year: 1949,
        rating: 4.7,
      },
      {
        key: 'b6',
        title: 'Cosmos',
        author: 'Carl Sagan',
        genre: 'Science',
        year: 1980,
        rating: 4.9,
      },
    ],
    { model: 'Book' },
  );

  await dcupl.init();

  // Create a list for the Book model
  const bookList = dcupl.lists.create<Book>({ modelKey: 'Book' });

  // Get all books
  const allBooks = bookList.catalog.query.items();
  console.log('All books:', allBooks.length);

  // Get the first book
  const firstBook = bookList.catalog.query.first();
  console.log('First book:', firstBook?.title);

  // Step 8: Filter by genre
  bookList.catalog.query.addCondition({
    attribute: 'genre',
    operator: 'eq',
    value: 'Science',
  });

  const scienceBooks = bookList.catalog.query.items();
  console.log('\nScience books:');
  scienceBooks.forEach((book) => {
    console.log(`  - ${book.title} by ${book.author}`);
  });

  // Step 9: Add a second condition (rating >= 4.5)
  bookList.catalog.query.addCondition({
    attribute: 'rating',
    operator: 'gte',
    value: 4.5,
  });

  const topScienceBooks = bookList.catalog.query.items();
  console.log('\nTop-rated science books (4.5+):');
  topScienceBooks.forEach((book) => {
    console.log(`  - ${book.title} (${book.rating})`);
  });

  // Step 10: Clear previous filters
  bookList.catalog.query.clear();

  // Get all books sorted by year (newest first)
  const sortedBooks = bookList.catalog.query.items({
    sort: { attributes: ['year'], order: ['DESC'] },
  });

  console.log('\nBooks by year (newest first):');
  sortedBooks.forEach((book) => {
    console.log(`  - ${book.year}: ${book.title}`);
  });

  // Paginate: get the first 3 books in the same order
  const firstPage = bookList.catalog.query.items({
    sort: { attributes: ['year'], order: ['DESC'] },
    start: 0,
    count: 3,
  });

  console.log('\nFirst page (3 books):');
  firstPage.forEach((book) => {
    console.log(`  - ${book.title}`);
  });

  // Step 11: Get facet counts for genres
  const genreFacets = bookList.catalog.fn.facets({ attribute: 'genre' });

  console.log('\nBooks by genre:');
  genreFacets.forEach((facet) => {
    console.log(`  - ${facet.value}: ${facet.size} books`);
  });
}

main();
```

## What you learned

In this tutorial, you learned how to:

1. Create a dcupl instance
2. Define a model with typed properties
3. Load data into the model
4. Initialize dcupl to process models and data
5. Create a list to query data
6. Filter data with conditions
7. Sort and paginate results
8. Get facet counts for building filter UIs

## Next steps

- [Build a Product Catalog](/learn/tutorials/product-catalog) — Create a filterable product catalog with faceted navigation
- [Core concepts](/sdk/core-concepts) — Understand models, properties, and references in depth
- [Querying](/sdk/queries/query-filter) — Learn all the filter operators available

## Troubleshooting

**Error: "Cannot find module '@dcupl/core'"**

Make sure you installed the package:

```bash
npm install @dcupl/core
```

**Error: "Model with key Book not found"**

Make sure you call `dcupl.models.set()` before `dcupl.init()`, that you create the list after `await dcupl.init()`, and that the `modelKey` you pass to `dcupl.lists.create()` matches the model's `key` exactly.

**Query returns no results**

Check that your filter values match exactly. Filters are case-sensitive by default; to ignore case, transform both sides to lowercase:

```typescript
bookList.catalog.query.addCondition({
  attribute: 'genre',
  operator: 'eq',
  value: 'science',
  options: { transform: ['lowercase'] },
});
```
