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
- Yarn
- pnpm
npm install @operativa/verse @operativa/verse-sqlite
yarn add @operativa/verse @operativa/verse-sqlite
pnpm add @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.
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:
- Typescript
- Console Log
await db.database.recreate();
⚡️ Executing SQL: Parameters: []
1| create table "Todo" (
2| "Id" integer not null,
3| "Title" varchar(255) not null,
4| "Completed" boolean not null,
5| primary key ("Id")
6| )
⚡️ Executing SQL: Parameters: [$1='Do the dishes', $2=0]
1| insert into "Todo" ("Title", "Completed") values (?, ?) returning "Id"
⚡️ Executing SQL: Parameters: [$1='Walk the dog', $2=0]
1| insert into "Todo" ("Title", "Completed") values (?, ?) returning "Id"
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.
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:
- Typescript
- Output
- Console Log
const todos = await db.from.todos.toArray();
todos.forEach(todo => {
console.log(`${todo.id}: ${todo.title} (completed: ${todo.completed})`);
});
1: Do the dishes (completed: false)
2: Walk the dog (completed: false)
⚡️ Executing SQL: Parameters: []
1| select "t0"."Id", "t0"."Title", "t0"."Completed"
2| from "Todo" as "t0"
🧭 Query executed in: 0.99ms
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:
- Typescript
- Output
- Console Log
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})`);
}
2: Walk the dog (completed: false)
⚡️ Executing SQL: Parameters: []
1| select "t1"."Id", "t1"."Title", "t1"."Completed"
2| from "Todo" as "t1"
3| where "t1"."Title" like '%dog%'
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:
- Typescript
- Console Log
// 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();
🧭 Query executed in: 0.87ms
⚡️ Executing SQL: Parameters: []
1| select "t2"."Id", "t2"."Title", "t2"."Completed"
2| from (
3| select "t1"."Id", "t1"."Title", "t1"."Completed"
4| from "Todo" as "t1"
5| where "t1"."Title" = 'Do the dishes'
6| ) as "t2"
7| limit 2
🧭 Query executed in: 1.03ms
⚡️ Executing SQL: Parameters: [$1='Do the dishes', $2=1, $3=1]
1| update "Todo" set "Title" = ?, "Completed" = ? where "Id" = ?
🧭 Commit executed in: 0.63ms
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.