Puerto

Getting Started

Puerto is a CLI tool that scaffolds production-ready Rust backends with Domain-Driven Design architecture. From install to running API in under a minute.

Prerequisites

  • Rust stable (1.85+) — install via rustup
  • Cargo (included with Rust)
  • Optional: PostgreSQL + SQLx CLI for database projects

Installation

Install the Puerto CLI from crates.io:

$ cargo install puerto

Verify the installation:

$ puerto --version

New Project

Create a new Puerto workspace. The interactive wizard asks for your project name, whether you need database support, and whether to include the Greeting demo entity.

$ puerto new my-app

For non-interactive use (CI/scripts):

$ puerto new --name my-app --db --no-demo

Generated structure:

my-app/
business/← domain + application
src/domain/greeting/
src/application/greeting/
infrastructure/← adapters
src/greeting/repository.rs
presentation/← http api
src/api/greeting/routes.rs
puerto.toml
Cargo.toml

Generate Scaffold

Scaffold a complete DDD entity across all layers. Puerto creates the domain model, repository trait, use cases, infrastructure adapter, and presentation routes — wired together automatically.

$ puerto generate scaffold User

What gets created:

business/model.rs, errors.rs, repository.rs (trait + mockall mock), 5 use cases (create, get, list, update, delete)
infrastructure/repository (InMemory or Pg — inferred from puerto.toml)
presentation/routes.rs, dto.rs, responses.rs, error_mapper.rs

Entity Fields

Define typed fields on your entities. The type system flows through every DDD layer — domain model, Params, DTOs, repository rows, and SQL migrations all derive from the field list.

From the CLI:

$ puerto generate scaffold Product name:String price:i64! sku:String

Append ! after a type to mark it as unique (e.g., sku:String! generates a unique DB constraint). Fields are persisted to puerto.toml and used by all generators.

Or edit puerto.toml directly:

[[entity]]
name = "Product"
use_cases = ["create_product", "list_products"]
[[entity.fields]]
name = "name"
type = "String"
[[entity.fields]]
name = "price"
type = "i64"
[[entity.fields]]
name = "sku"
type = "String"
unique = true

Supported types:

Rust Type SQL Type OpenAPI
StringTEXTstring
i64BIGINTinteger(int64)
boolBOOLEANboolean
f64DOUBLEnumber(double)
UuidUUIDstring(uuid)
DateTime<Utc>TIMESTAMPTZstring(date-time)
Option<T>nullableT?
Vec<String>TEXT[]array[string]
HashMap<String, String>JSONBobject

Every entity always includes id, created_at, updated_at, deleted, and deleted_at as system fields — these are generated automatically and should not be added to puerto.toml.

If entity.fields is empty or absent, entities default to a single name: String field for backward compatibility.

Validate your configuration:

$ puerto validate

Checks entity names (PascalCase), field names (snake_case), field types (against the type registry), duplicate entities and fields, and warns about Option<T> fields marked unique.

Layer Generators

Generate one DDD layer at a time. Ideal for AI-assisted development — keeping each agent focused on a single layer prevents context drift and makes compiler errors immediately actionable.

$ puerto generate domain Product
$ puerto generate application Product
$ puerto generate repository Product
$ puerto generate presentation Product
domainmodel, errors, repository trait, 5 use case traits, ProductMother (Object Mother) — registers entity in puerto.toml
application5 use case implementations with unit tests
repositoryInMemory or Pg adapter — inferred from puerto.toml
presentationroutes, dto, responses, error mapper — regenerates bootstrap.rs

Each command validates the prior step completed and prints a Next: hint. Use puerto generate scaffold when you want all layers at once.

Add a Use Case

Add a single use case to an existing entity. Puerto generates the trait, implementation, and unit tests.

$ puerto generate use-case User create

Database (SQLx)

Puerto has first-class SQLx + Postgres support. Enable it when creating a project — Puerto infers the choice for every subsequent scaffold automatically.

$ puerto new --name my-app --db
$ puerto generate scaffold User
$ puerto generate migration create_users_table

Puerto reads puerto.toml to decide: db projects get a PgUserRepository with a connection pool and auto-created migration; non-db projects get InMemoryUserRepository. No flag needed on every scaffold.

Architecture

Puerto enforces a strict dependency rule: dependencies only point inward. The domain layer has zero external dependencies.

dependency flow (inward only)
presentation/ → depends on infrastructure/
infrastructure/ → depends on business/
business/ → depends on nothing (std + thiserror only)

IDE Snippets

Every puerto new project ships with 23 Puerto-adapted Rust snippets for Zed, VS Code, and nvim+LuaSnip. No configuration needed — files are written automatically and loaded by your editor from the project root.

Zed

Snippets are written to .zed/snippets/rust.json. Zed loads project-local snippets automatically — no further setup.

VS Code

Snippets are written to .vscode/puerto.code-snippets. VS Code picks up workspace snippet files automatically.

nvim + LuaSnip

The VS Code file uses TextMate format, which LuaSnip can load directly. Add to your config:

require("luasnip.loaders.from_vscode").lazy_load({ paths = {"./.vscode"} })

Available snippets

Domain
domain-model Entity struct + Props + new(props) + from_repository()
domain-errors thiserror enum with machine-readable error codes
repository-trait RepositoryTrait + mockall mock in pub mod mocks
domain-use-case Params struct + UseCaseTrait
Application
app-use-case UseCaseImpl with LoggerTrait injection + unit tests included
Infrastructure
persistence-entity DB row struct (FromRow) + TryFrom/From conversions
persistence-repo PgEntityRepository — find_by_id + save with SQLx macros
Tests
should-do-test #[tokio::test] with Arrange / Act / Assert comments
sqlx-test #[sqlx::test(migrations = "migrations")] — real Postgres pool
sqlx-repo-test-module Full integration test module for PgEntityRepository
object-mother Object Mother builder: random(), with_name(), build()

+ 9 more: lib.rs blocks, poem DTOs, response enums, error mappers, API struct

Switching IDE or regenerating

Run puerto generate snippets from your project root at any time to write both IDE files. Use --ide to target a specific editor:

$ puerto generate snippets
$ puerto generate snippets --ide zed
$ puerto generate snippets --ide vscode