A data layer that speaks Go
Generics-based CRUD, eager loading, joins, and transactions. Pair typed repositories with an expressive, dialect-aware SQL builder.
Explore the repositoryTHE FULL-STACK GO WEB FRAMEWORK
The productivity of Rails. The simplicity of Go.
One thoughtful framework for your next big idea.
From your first route to your data, views, and storage.
All the pieces, already connected.
// A real API. A little Go.
func IndexAction(c *gin.Context) {
posts, err := repo.FindBy[models.Post](
sql.H{"active": true},
)
if err != nil {
render.Error(c, err)
return
}
render.OK(c, posts)
}// Your HTML belongs in Go, too.
package home
templ Greeting(name string) {
<main>
<h1>Hello, { name }.</h1>
<p>Your next idea starts here.</p>
</main>
}
// Render from your Gin action:
render.HTML(c, home.Greeting("world"))# Give your idea a little structure.
go run . cli generate api posts
go run . cli generate model post
go run . cli generate service post title:string
# Evolve your database.
go run . cli generate migration create_posts
go run . cli db:migrate
# Explore your data interactively.
AIRWAY_ENV=local go run . repl01 / A FRAMEWORK THAT FITS TOGETHER
Stop assembling the essentials.
Start building what makes your app yours.
Generics-based CRUD, eager loading, joins, and transactions. Pair typed repositories with an expressive, dialect-aware SQL builder.
Explore the repositoryCompose type-safe, server-rendered views with templ. Keep your pages and your API in the same language, in the same application.
Meet templ viewsGenerate APIs, actions, models, and services from the CLI. Evolve your schema with migrations and explore your data in the REPL.
Find your workflowOne storage API for local files, Amazon S3, Cloudflare R2, and Tencent COS. Change your backend through configuration.
Explore storageGin handles your HTTP routes. Built-in WebSocket pub/sub connects your clients. Add Redis when your application needs it.
See the endpointsBuild a self-contained Go binary or a Docker image. Pure-Go SQLite needs no CGO. Deploy under a sub-path behind your reverse proxy.
Take it to productionUse the whole framework. Or just the parts you love. Reusable libraries under lib/, a runnable application skeleton, and room to make it your own.
02 / YOUR DATA, YOUR CHOICE
Start small with SQLite. Connect to PostgreSQL or MySQL 8. Airway infers the driver from your connection string, so your basic CRUD code stays with you.
See database configuration# Local development, zero database services.
DSN="sqlite://./tmp/airway.db"# Connect to your PostgreSQL database.
DSN="postgres://user:pass@localhost:5432/airway"# Connect to your MySQL 8 database.
DSN="mysql://user:pass@localhost:3306/airway"repo.FindBy[Post](sql.H{"active": true})Basic CRUD is portable. Some advanced SQL helpers are PostgreSQL-specific.
03 / THE FRONTEND, WITHOUT NODE.JS
Pages stay server-rendered with templ — structure, SEO, and first paint included. Where a page needs to react, drop in an island: a Preact component mounted on one small spot of the page, seeded with props from your Go action. Fetching, resolving, bundling, and serving npm packages happens inside the airway binary itself.
// In any templ view — an island, seeded server-side:
@assets.Island("chart", map[string]any{
"points": []int{4, 8, 15}),
// What ships to the browser:
<div data-island="chart"></div>
<script type="application/json" id="island-data-1">
{"points":[4, 8, 15]}
</script>
// The runtime scans data-island nodes and mounts the
// component with the adjacent JSON as props.// app/assets/js/islands/chart.tsx — the default export
// is the island; its file path is its name.
import { DataTable, useToast } from "../ui";
export default function Chart({ posts }: { posts: Post[] }) {
const toast = useToast();
return (
<DataTable
data={posts}
pageSize={10}
onRowClick={(post) => toast.show(post.title)}
/>
);
}
// React-ecosystem logic, an in-house visual layer.
// See every component live at /ui on your server.# Frontend dependencies, no Node required:
airway js:add @tanstack/react-table@9.2.4 # pinned in js.pkg.json + lock
airway js:install # node_modules layout, committed
airway js:build # esbuild -> app/assets/dist
# Scaffold the interactive parts:
airway generate island chart # a new island component
airway generate scaffold post title:string # CRUD: API + page + island
# AIRWAY_ENV=local airway server
# -> in-memory rebuilds, livereload on every saveOutgrow the islands? Split the frontend into its own app and keep Airway as your JSON API — the escape hatch is part of the design.
04 / CLEAR FOR TAKEOFF
A few commands. A working application.
And a whole lot of possibility.
Clone Airway and copy the example config.
Uncomment the SQLite DSN in your .env file.
Start the app and open localhost:1900.
# 1. Get the code
git clone https://github.com/daqing/airway.git
cd airway
cp .env.example .env
# 2. In .env, uncomment the SQLite DSN:
# DSN="sqlite://./tmp/airway.db"
# 3. Start your application
AIRWAY_ENV=local go run .Requires Go 1.27.1 or later · Live reload with just dev
OPEN SOURCE. OPEN POSSIBILITIES.
Read the code. Open an issue. Make your first contribution.
There’s a place for your ideas in Airway.