.md

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 (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 (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):

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:

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:

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:

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:

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:

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:

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:

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.

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:

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:

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:

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.

Step 10: sort and paginate

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

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:

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:

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

Troubleshooting

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

Make sure you installed the package:

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:

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