Skip to main content

Getting Started

Let's get started with the basics of Verse by creating a simple todo list application that uses a SQLite database.

For this example, we will use SqlLite with the @operativa/verse-sqlite driver. Verse supports many database drivers, including PostgreSQL, MySQL, and SQLite. You can find the list of supported drivers in the database drivers section.

Create a new directory for your project and install the packages:

npm install @operativa/verse @operativa/verse-sqlite

Now, create a new file called index.ts and add the following code:

import { verse } from "@operativa/verse";
import { sqlite } from "@operativa/verse-sqlite";
import { boolean, entity, int, string } from "@operativa/verse/model/builder";
import { PrettyConsoleLogger } from "@operativa/verse/utils/logging";

// Define a simple entity to represent a Todo item.

const Todo = entity(
{
id: int(),
title: string(),
completed: boolean(),
},
builder => {
builder.data(
{ title: "Do the dishes", completed: false },
{ title: "Walk the dog", completed: false }
);
}
);

Here, we have created a simple Todo entity with a title and completed property.

Verse uses a simple builder pattern to define the model, which makes it easy to define rich models in a natural way, all in pure TypeScript. We also added seed data to the todos entity using the data method on the builder passed to an optional configuration function.

The id property will be treated as the primary key by convention and Verse will take care of managing the identifier for us.

note

In Verse, entities can also be classes. You can do this by passing a class constructor to the entity function. This is useful when you want to define methods and other behavior on your entities.

Next, we will configure Verse, define our model and add some seed data. Add the following code to the index.ts file:

// Setup our Verse instance.

const db = verse({
config: {
driver: sqlite("todos.sqlite"),
logger: new PrettyConsoleLogger(),
},
model: {
entities: {
todos: Todo,
},
},
});

This code configures Verse to use the @operativa/verse-sqlite package as the database driver and sets up our simple model with a single entity labelled todos (labels are used to refer to entities in queries and other operations).

Now, we need to create the todo.sqlite database schema we configured above. Add the following:

await db.database.recreate();

This will create the database file and the necessary tables for our model, and also insert the seed data we defined earlier. The recreate method is a convenience method that drops the database if it exists and then creates it again. It is useful for development and testing purposes.

note

This is a simple way to create the database schema for our model. In a real-world application, you would typically use Verse's migration system to manage the database schema.

Next, we will write our first query to retrieve all the todos from the database. Add the following code to the index.ts file:

const todos = await db.from.todos.toArray();

todos.forEach(todo => {
console.log(`${todo.id}: ${todo.title} (completed: ${todo.completed})`);
});

Verse provides a simple and powerful query API that allows you to write queries in a natural way using TypeScript. The from property is used to specify the entity to query, and the toArray method is used to execute the query and return the results as an array. We then iterate over the results and log the todos to the console.

We can also write more complex queries using the query API. For example, let's write a query to retrieve only todos that mention "dog" in the title:

const query = db.from.todos.where(todo => todo.title.like("%dog%"));

for await (const todo of query) {
console.log(`${todo.id}: ${todo.title} (completed: ${todo.completed})`);
}

Observe that in this case, instead of using the toArray method, we used a for await loop to iterate over the results. This is because the query API supports asynchronous iteration, which allows you to efficiently process result sets without loading the entire result set into memory.

Verse supports a wide range of query operations, including filtering, sorting, grouping, and aggregation. You can also write custom SQL queries if you need to. See the Query API documentation for more information.

Now let's see how easy it is to modify data using Verse. For example, let's mark the "Do the dishes" todo as completed:

// Modify a todo and save the changes.

const uow = db.uow();

const todo = await uow.todos
.where(todo => todo.title === "Do the dishes")
.single();

todo.completed = true;

await uow.commit();

Verse uses a Unit of Work (UoW) pattern to manage updates to the database. The uow method is used to create a new unit of work, and the commit method is used to save any changes to the database. The UoW pattern is a powerful way to manage database updates in a transactional way, allowing you to focus on your business logic.