Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/Aking16/timify/llms.txt

Use this file to discover all available pages before exploring further.

Timify stores all data in a local SQLite file through the @libsql/client driver, with Drizzle ORM providing type-safe query building on top. There is no external database server to provision — the entire database is a single file on disk, making Timify easy to self-host and run fully offline.

How the Database Connection Works

The database client is created in src/db/index.ts using @libsql/client and wrapped with drizzle-orm/libsql. The file path is read from the DB_FILE_NAME environment variable at startup:
src/db/index.ts
import { createClient } from "@libsql/client";
import { drizzle } from "drizzle-orm/libsql";

const client = createClient({
  url: process.env.DB_FILE_NAME || "file:./src/db/local.db",
});

export const db = drizzle(client);
The file: prefix in the URL is required by the libsql protocol — it tells the client to open a local file rather than connect to a remote server.
Because Timify uses @libsql/client (not the better-sqlite3 synchronous driver), database calls are asynchronous and fully compatible with React Server Components and Next.js Server Actions.

Configuring the Database Path

Set DB_FILE_NAME in your .env file to control where the SQLite file is stored:
DB_FILE_NAME
string
default:"file:./src/db/local.db"
The libsql URL pointing to the SQLite file. Must start with file:. The path after file: is resolved relative to the project root when using a relative path, or as an absolute filesystem path when starting with /.
# Relative path (default) — file lives inside the project
DB_FILE_NAME=file:./src/db/local.db

# Absolute path — recommended for production
DB_FILE_NAME=file:/var/data/timify/timify.db

Drizzle ORM and Schema

Timify uses Drizzle ORM for all database queries. The schema is defined in src/db/schema.ts, and drizzle.config.ts tells drizzle-kit where to find it:
drizzle.config.ts
import { defineConfig } from "drizzle-kit";

export default defineConfig({
  out: "./drizzle",
  schema: "./src/db/schema.ts",
  dialect: "sqlite",
  dbCredentials: {
    url: process.env.DB_FILE_NAME || "file:./src/db/local.db",
  },
});
Generated migration files are placed in the ./drizzle/ directory at the project root.

Managing the Schema

Use the following drizzle-kit commands to work with the database:
1

Apply the schema to the database

Push all schema changes from src/db/schema.ts directly to the SQLite file. This is the primary development workflow — it applies changes in-place without generating migration files.
npx drizzle-kit push
2

Browse data with Drizzle Studio

Open the Drizzle Studio web UI to inspect and edit rows in every table through a browser interface.
npx drizzle-kit studio
Drizzle Studio starts a local server (usually at https://local.drizzle.studio) and connects to the database file configured in drizzle.config.ts.

Database Tables

The schema is split into two groups: auth tables managed entirely by better-auth, and application tables defined in src/db/schema.ts.

Auth Tables

These four tables are created and owned by better-auth. Do not modify their columns manually.
ColumnTypeNotes
idtext PK
nametextDisplay name, not null
emailtextUnique, not null
email_verifiedinteger (boolean)Defaults to false
imagetextAvatar URL, nullable
created_atinteger (timestamp)Defaults to unixepoch()
updated_atinteger (timestamp)Auto-updated on change
ColumnTypeNotes
idtext PK
expires_atinteger (timestamp)Not null
tokentextUnique session token
ip_addresstextNullable
user_agenttextNullable
user_idtext FKReferences user.id, cascades on delete
created_atinteger (timestamp)
updated_atinteger (timestamp)Auto-updated on change
Used by better-auth for email verification flows.
ColumnTypeNotes
idtext PK
identifiertextUnique — email or other identifier
valuetextThe verification token
expires_atinteger (timestamp)Not null
created_atinteger (timestamp)
updated_atinteger (timestamp)Auto-updated on change

Application Tables

These tables are defined in src/db/schema.ts and hold all Timify-specific data.
ColumnTypeNotes
idtext PKUUID generated by crypto.randomUUID()
user_idtext FKReferences user.id, cascades on delete
nametextNot null
descriptiontextNullable
colortextHex color, defaults to #3b82f6
hourly_raterealDefaults to 0
is_activeinteger (boolean)Defaults to true
created_atinteger (timestamp)
updated_atinteger (timestamp)Auto-updated on change
ColumnTypeNotes
idtext PKUUID
user_idtext FKReferences user.id, cascades on delete
project_idtext FKReferences projects.id, set null on delete
titletextNullable
descriptiontextNullable
start_timeinteger (timestamp)Defaults to unixepoch()
end_timeinteger (timestamp)Nullable — null when timer is running
durationintegerSeconds; calculated field
is_runninginteger (boolean)Defaults to true
billableinteger (boolean)Defaults to false
hourly_raterealNullable — overrides project rate when set
created_atinteger (timestamp)
updated_atinteger (timestamp)Auto-updated on change
ColumnTypeNotes
idtext PKUUID
user_idtext FKReferences user.id, cascades on delete
nametextNot null
colortextHex color, defaults to #9ca3af
created_atinteger (timestamp)
updated_atinteger (timestamp)Auto-updated on change
Links time entries to tags. The pair (time_entry_id, tag_id) is unique.
ColumnTypeNotes
idtext PKUUID
time_entry_idtext FKReferences time_entries.id, cascades on delete
tag_idtext FKReferences tags.id, cascades on delete

Production Considerations

SQLite is an excellent fit for single-user or low-concurrency self-hosted deployments. Keep the following in mind when running Timify in production:
SQLite does not support concurrent writes from multiple processes. Timify must run as a single instance — do not point multiple Node.js workers or containers at the same database file, as this will cause write-lock conflicts.
Backup strategy: Because all data lives in a single file, backing up is as simple as copying it. Schedule a regular cp or rsync of the database file to a separate location — for example:
cp /var/data/timify/timify.db /backups/timify-$(date +%Y%m%d).db
Use an absolute path for DB_FILE_NAME in production so the database location is independent of the working directory:
.env (production)
DB_FILE_NAME=file:/var/data/timify/timify.db
Ensure the directory exists and is writable by the process running Timify before starting the app.

Build docs developers (and LLMs) love