Airway CLI Scaffolding Guide
Airway ships a scaffolding CLI. Install it globally with:
go install github.com/daqing/airway@latestThis gives you the airway command. Commands auto-load .env from the current project root. Inside a project (or the framework repo itself) the same commands also work as go run . <command> — and some commands (repl, engine:install) should be run that way, because they only see the models and engines compiled into the running binary (see below).
The legacy form airway cli <command> still works as a compatibility alias.
Command Overview
airway new <module-path> # scaffold a new project skeleton
airway server # start the HTTP server
airway db:create
airway db:drop
airway db:migrate [version]
airway db:rollback [step]
airway db:status
airway engine new <module-path> # scaffold a new engine module
airway engine:list
airway engine:install [name]
airway generate [action|api|model|migration|service|cmd] [params]
airway plugin install /path/to/project # deprecated; use engines (docs/engine.md)
airway schema:dump
airway schema:show
airway upload /path/to/file
airway repl
airway versionRunning airway with no arguments prints usage.
Start a new project
airway new myapp # directory: myapp
airway new github.com/me/myapp # module path; directory is the last path segmentairway new generates a fresh project skeleton based on the framework's app/ scaffold, runs go mod tidy, and prints the follow-up steps:
cd myapp
cp .env.example .env # set DSN and PORT
airway db:create
airway db:migrate
go run . # starts the server (same as: go run . server)A generated project's binary starts the HTTP server when run with no arguments (or with server), and dispatches any other arguments to the built-in CLI.
Start the server
airway server # or, from source: go run . serverThe framework repository's own main.go no longer starts the server by default — use go run . server when developing Airway itself. The Docker image already runs the binary with server.
Upload a file
Upload a local file using the storage configuration from .env:
airway upload /tmp/foo.pngThe source path becomes a root-relative storage key. In this example the key is tmp/foo.png (shown as /tmp/foo.png by the command). The content size is read from the file and its content type is determined from the extension or file contents.
To choose the storage key explicitly, pass it before the local file path:
airway upload images/foo.png /tmp/foo.pngCode Generators
Generators read the module path from the current directory's go.mod, so the generated service/cmd code imports your project's own app/models and app/services packages — no hard-coded framework paths.
Generate an API module
airway generate api adminThis creates:
app/api/admin_api/routes.goapp/api/admin_api/index_action.go
Generated route shape:
func Routes(r *gin.RouterGroup) {
g := r.Group("/admin")
{
g.GET("/index", IndexAction)
}
}Use this when you want to create a new API namespace quickly.
Generate an action inside an existing API module
airway generate action admin showThis creates:
app/api/admin_api/show_action.go
Use this when the API folder already exists and you only need a new endpoint handler.
Generate a model
airway generate model postThis creates:
app/models/post.go
The generated model includes:
ID,CreatedAt,UpdatedAtTableName()- REPL registration via
registerREPLModel
Generate a service
airway generate service post title:string published:boolThis creates:
app/services/post.go
The generated file includes:
FindPostCreatePostUpdatePostDeletePost
Field arguments use name:type format.
Generate a command helper
airway generate cmd post title publishedThis creates:
cmd/post.go
This generator is useful if your project exposes extra custom CLI helpers around generated services.
Generate a migration
airway generate migration create_postsThis creates a pair of timestamped SQL files under db/migrate/:
<timestamp>_create_posts.up.sql— the forward migration<timestamp>_create_posts.down.sql— the rollback migration
Both files contain commented-out CREATE TABLE / DROP TABLE examples to get you started; edit them to define your real schema.
The older Go DSL migration mechanism (schema.RegisterChange in lib/migrate/schema) is still supported, but DSL migrations only take effect when they are compiled into the binary that runs the migration. When the CLI finds timestamp-named .go migration files under ./db/migrate, it prints a warning to remind you of this.
Migration Commands
Run all pending migrations
airway db:migrateMigrate to a specific version
airway db:migrate 20260327120000Roll back the latest migration
airway db:rollbackRoll back multiple steps
airway db:rollback 3Show migration status
airway db:statusMigration commands read:
AIRWAY_DB_DSNAIRWAY_PGas a legacy fallback
In normal local development, these values can come directly from your project's .env file because the CLI loads it automatically. The migration commands use the current Airway DSN and work with the databases supported by the project, including PostgreSQL, MySQL, and SQLite.
Engine Commands
Scaffold a new engine module (a standalone Go module; see docs/engine.md):
airway engine new im # directory: im, engine name: im
airway engine new github.com/me/airway-im-engine # name derived from the last path segmentUnlike the commands below, engine new works fine with the globally installed airway — it writes files and does not depend on compile-time registration.
Engines are optional feature modules enabled with blank imports in engines.go (see docs/engine.md):
go run . engine:list # list registered engines and mount paths
go run . engine:install <name> # copy an engine's embedded SQL migrations into db/migrateEngines register at compile time, so run these through the project binary (go run . ... in the project directory): the globally installed airway can only list and install the engines compiled into itself.
engine:install assigns fresh timestamps to the copied migrations and skips files that are already installed; afterwards they are ordinary migrations managed by db:migrate / db:rollback / db:status.
REPL
go run . replThe REPL only sees the models compiled into the binary you run — project models register through registerREPLModel in app/models, which delegates to github.com/daqing/airway/lib/replreg. Use go run . repl inside your project; the globally installed airway repl only sees the framework's built-in models.
Plugin Installation
Deprecated:
plugin installwill be removed in a future release. Use the Engine mechanism instead (see docs/engine.md).
Install the current project as a plugin into another Airway project:
airway plugin install /path/to/projectThis copies:
./app/*into/path/to/project/app/./cmd/*into/path/to/project/cmd/./db/migrate/*.sqlinto/path/to/project/db/migrate/
Migration files copied through plugin install are prefixed with a fresh timestamp to avoid version collisions.
Practical Example
Here is a minimal workflow for adding a posts feature from scratch.
Step 1. Generate the database migration
airway generate migration create_postsThen edit the generated .up.sql file in db/migrate/ and define the table you need.
Example:
CREATE TABLE posts (
id BIGSERIAL PRIMARY KEY,
title TEXT NOT NULL,
published BOOLEAN NOT NULL DEFAULT FALSE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);Run the migration:
airway db:migrateStep 2. Generate the model
airway generate model postThis creates app/models/post.go.
At this point you will usually extend the generated struct with your real fields, for example:
type Post struct {
ID sql.IdType `db:"id" json:"id"`
Title string `db:"title" json:"title"`
Published bool `db:"published" json:"published"`
CreatedAt time.Time `db:"created_at" json:"created_at"`
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
}Step 3. Generate the service
airway generate service post title:string published:boolThis creates app/services/post.go with basic CRUD helpers.
Step 4. Generate the API module
airway generate api post
airway generate action post create
airway generate action post showThis gives you:
app/api/post_api/routes.goapp/api/post_api/index_action.goapp/api/post_api/create_action.goapp/api/post_api/show_action.go
Step 5. Wire the API routes into the router
Open config/routes.go and import the generated package:
import (
"github.com/gin-gonic/gin"
"github.com/daqing/airway/app/api/post_api"
"github.com/daqing/airway/app/api/health_api"
"github.com/daqing/airway/app/websocket"
)Then register it inside apiGroupRoutes:
func apiGroupRoutes(r *gin.Engine) {
v1 := r.Group("/api/v1")
{
post_api.Routes(v1)
}
}With the generated default route file, you will get an endpoint like:
GET /api/v1/post/indexStep 6. Fill in the generated action logic
For example, create_action.go is only a scaffold. You still need to:
- define request params
- call
services.CreatePost(...) - return data through
render.OK(...)orrender.Error(...)
Step 7. Run the app
justOr:
go run . serverAt that point you have the full skeleton for:
- database migration
- model
- service
- API handlers
- route registration
Notes
- Generators do not overwrite existing files. If the target file already exists, the command returns
file already exists. generate apicreates files, but you still need to wire the generatedRoutes(...)into your router setup.generate serviceassumes your project has anapp/servicespackage.- Generated files are starting points. They are meant to be edited after creation.