Skip to content

Project Structure

After running alab init, you get this structure:

myapp/
├── alab.yaml # Configuration
├── schemas/ # Schema definitions
├── migrations/ # Generated migrations
└── types/ # IDE support files
PathPurposeEditable?
alab.yamlProject config (database, output paths)Yes
schemas/Your schema definitionsYes
migrations/Generated SQL filesNo*
types/Generated TypeScript typesNo

*Migrations can be edited for custom SQL, but regenerating will overwrite.

Astroladb uses a namespace-based organization where folder structure determines table prefixes.

Folders inside schemas/ become namespaces:

schemas/
├── auth/
│ ├── user.js → auth.user → SQL table: auth_users
│ └── session.js → auth.session → SQL table: auth_sessions
├── blog/
│ ├── post.js → blog.post → SQL table: blog_posts
│ └── comment.js → blog.comment → SQL table: blog_comments
└── catalog/
├── product.js → catalog.product → SQL table: catalog_products
└── category.js → catalog.category → SQL table: catalog_categories
  • File names: snake_case, singular (e.g., user.js, post_comment.js)
  • References: Use dot notation with namespace (e.g., "auth.user")
  • SQL tables: Auto-generated as {namespace}_{table_name} (e.g., auth_user)

Namespaces provide logical grouping and prevent naming conflicts:

  • auth: User authentication and authorization
  • blog: Content management
  • catalog: Products and inventory
  • orders: E-commerce transactions
  • analytics: Tracking and reporting

This makes it easy to understand what each table does at a glance.

A schema file is a JavaScript module that exports a table definition:

schemas/auth/user.js
export default table({
id: col.id(),
email: col.email().unique(),
username: col.username().unique(),
password: col.password_hash(),
role: col.enum(["admin", "user"]).default("user"),
is_active: col.flag(true),
}).timestamps();
ElementSyntaxExample
Table definitiontable({ ... })table({ id: col.id() })
Column typescol.type()col.email(), col.name()
Nullable.optional()col.bio().optional()
Unique constraint.unique()col.email().unique()
Default value.default(value)col.status().default("active")
Relationshipscol.belongs_to("ns.table")col.belongs_to("auth.user")
Timestamps.timestamps()Adds created_at, updated_at
Soft delete.soft_delete()Adds deleted_at
Indexes.index("col1", "col2")Composite index
Unique composite.unique("col1", "col2")Multi-column uniqueness

Every column follows this pattern:

columnName: col.semanticType().modifier1().modifier2()

Examples:

email: col.email().unique(),
bio: col.text().optional(),
price: col.money().min(0),
category: col.belongs_to("catalog.category").on_delete("restrict"),

The alab.yaml configures your project:

alab.yaml
database:
dialect: postgres
url: postgres://user:pass@localhost:5432/mydb
schemas: ./schemas
migrations: ./migrations