Quick Start
In this guide you create a Holo-JS project, add a Post model with a database table, and return posts from an API route. It takes about ten minutes.
You need Node.js 22.18 or newer on the 22 line, or Node.js 24 or 26. The project uses SQLite, so there is no database server to set up. On Node.js 22, Nuxt projects installed with npm need npm 11; see Installation.
1. Create the project
npm create holo-js@latest my-apppnpm create holo-js my-appyarn create holo-js my-appbun create holo-js my-appThe installer asks a few questions:
| Question | Pick for this guide |
|---|---|
| Framework | next, nuxt, or sveltekit |
| Database driver | sqlite |
| Package manager | the one you use |
| Optional packages | none (press Enter) |
Holo creates the my-app folder, writes the project files, and installs dependencies with the package manager you picked.
TIP
The installer asks for the package manager separately, even if you ran it with pnpm create or bun create. Pick the same one so the lockfile matches.
2. Start the dev server
cd my-app
npm run devUse pnpm dev, yarn dev, or bun run dev if you picked a different package manager.
Open the URL your framework prints: http://localhost:3000 for Next.js and Nuxt, http://localhost:5173 for SvelteKit. You see the starter page.
Leave the dev server running. Open a second terminal in my-app for the next steps.
3. Create a model and migration
npx holo make:model Post --migration --seederThis creates three files:
server/models/Post.ts: the modelserver/db/migrations/<timestamp>_create_posts_table.ts: a migration that creates thepoststableserver/db/seeders/PostSeeder.ts: a seeder for sample data
4. Define the table
Open the new migration and add columns for a title and body:
import { defineMigration, type MigrationContext } from '@holo-js/db'
export default defineMigration({
async up({ schema }: MigrationContext) {
await schema.createTable('posts', (table) => {
table.id()
table.string('title')
table.text('body')
table.timestamps()
})
},
async down({ schema }: MigrationContext) {
await schema.dropTable('posts')
},
})Run the migration:
npx holo migrateHolo creates the posts table in storage/database.sqlite and regenerates the database types, so the model now knows about title and body.
5. Allow mass assignment
Open the model and list the columns that Post.create() may fill:
import { defineModel } from '@holo-js/db'
export default defineModel('posts', {
fillable: ['title', 'body'],
})create() throws an error if you pass a column that is not in fillable. This protects you from users setting fields like is_admin through a form.
6. Add sample data
Replace the seeder body with a couple of posts:
import { defineSeeder } from '@holo-js/db'
import Post from '../../models/Post'
export default defineSeeder({
name: 'PostSeeder',
async run() {
await Post.create({ title: 'Hello Holo', body: 'My first post.' })
await Post.create({ title: 'Second post', body: 'Models are typed end to end.' })
},
})Run it:
npx holo seed7. Query posts in a route
Create an API route for your framework. Import the model and query it like any other async function.
// app/api/posts/route.ts
import Post from '@/server/models/Post'
export async function GET() {
const posts = await Post.latest().get()
return Response.json(posts)
}// server/api/posts.get.ts
import Post from '../models/Post'
export default defineEventHandler(async () => {
return await Post.latest().get()
})// src/routes/api/posts/+server.ts
import Post from '../../../../server/models/Post'
export async function GET() {
const posts = await Post.latest().get()
return Response.json(posts)
}Open /api/posts in your browser (for example http://localhost:3000/api/posts). You see both posts as JSON. latest() sorts them by created_at, newest first. The seeder creates both posts in the same second, so on a database that stores whole seconds, such as MySQL, they can come back in either order.
Try typing Post.where(' in your editor: title, body, and the other columns autocomplete.
What you built
- A model in
server/modelsthat Holo discovers automatically. - A migration that Holo tracks and can roll back with
npx holo migrate:rollback. - A seeder you can re-run on a fresh database with
npx holo migrate:fresh --seed. - An API route that uses your framework's normal routing and Holo's ORM.
Next steps
- Tutorial: build a full blog with auth, admin pages, and uploads.
- Project Structure: what each folder in
my-appis for. - Querying Models: filters, ordering, pagination, and more.
- Migrations: all column types and table changes.
- Your framework guide: Next.js, Nuxt, or SvelteKit.