<div align="center">

# moonway

**The road to the moon.**

A full-stack web framework for [MoonBit](https://www.moonbitlang.com) — routing,
database, caching, WebSocket, an auto-generated admin dashboard, and more.
Everything a modern web app needs, designed to work together from your first
`moon new`.

[![CI](https://github.com/daqing/moonway/actions/workflows/ci.yml/badge.svg)](https://github.com/daqing/moonway/actions/workflows/ci.yml) · [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE) · [English](README.mbt.md) | [简体中文](README.zh-CN.md)

</div>

## Why moonway?

MoonBit is fast, type-safe, and a joy to write — but assembling a real web
application today means picking a router, wiring a database client, bolting on
a cache, and writing the glue yourself. **moonway** is batteries-included: one
cohesive framework where the pieces of a modern web app are designed to fit
together, so you can go from idea to running application in minutes.

The name is the mission: **moon + way — the road to the moon.**

## Features

- **HTTP routing** — expressive routes with path parameters and a composable
  middleware pipeline.
- **Database layer** — a typed data abstraction with migrations and a fluent
  query API. SQLite works out of the box.
- **Caching** — Redis-backed response caching with a configurable TTL
  (`app.cache(...)`); requests are served untouched when Redis is
  unavailable.
- **WebSocket** — first-class support for realtime features like chat and live
  updates.
- **Admin dashboard** — a CRUD admin UI generated from your models. Zero
  frontend work required.
- **REPL console** — an interactive console to query data and poke at your
  running app.
- **Code generator** — scaffolding for models, handlers, and more, so
  boilerplate never slows you down.
- **Frontend included** — an integrated [Preact](https://preactjs.com) workflow
  for the client side of your app.

## Quick start

Install the [MoonBit toolchain](https://docs.moonbitlang.com), then:

```bash
moon new hello-moonway
cd hello-moonway
# moonway is a server-side framework: switch the project to the native backend
awk '{sub(/preferred_target = "wasm"/, "preferred_target = \"native\"")} 1' moon.mod > moon.mod.tmp && mv moon.mod.tmp moon.mod
moon add daqing/moonway
# register the dependency in the main package too
printf 'import {\n  "daqing/moonway",\n}\n' >> cmd/main/moon.pkg
```

Give your app a route:

```moonbit nocheck
///|
fn main {
  let app = @moonway.new()
  app.get("/", ctx => ctx.text("Hello, moonway!"))
  app.listen(3000)
}
```

Run it:

```bash
moon run cmd/main
```

Open <http://localhost:3000>. You're on the road. 🌙

## The full stack, in one file

Replace `cmd/main/moon.pkg` with (the link flags are required — `moon add`
does not inherit them from dependencies; Linux also needs
`apt install libsqlite3-dev`, macOS ships SQLite):

```json
import {
  "daqing/moonway",
  "daqing/moonway/db",
  "moonbitlang/core/json",
}

supported_targets = "native"

pkgtype(kind: "executable")

options(
  link: { "native": { "cc-link-flags": "-lsqlite3" } },
)
```

Then replace `cmd/main/main.mbt` with:

```moonbit nocheck
///|
struct Post {
  id : Int32
  title : String
  body : String
} derive(ToJson, FromJson)

///|
let posts : @db.Table = @db.table("posts", [
  @db.column("id", @db.IntT, primary=true),
  @db.column("title", @db.TextT),
  @db.column("body", @db.TextT),
])

///|
fn main {
  let app = @moonway.new()
  let db = @moonway.sqlite("app.db")
  if !db.migrate([@db.migration(1, "create posts", [posts.create_sql()])]) {
    abort("migrations failed")
  }
  app.get("/posts", ctx => {
    let items : Array[Json] = db.select_raw(posts, "1=1", [], limit=100)
    ctx.json(Json::array(items))
  })
  app.post("/posts", ctx => {
    let row : Json? = Some(@json.parse(ctx.body())) catch { _err => None }
    match row {
      Some(Object(fields)) =>
        if db.insert_raw(posts, fields) {
          ctx.status(201).json(Json::object(fields))
        } else {
          ctx.status(500).text("insert failed")
        }
      _ => ctx.status(400).text("invalid JSON body")
    }
  })
  // A ready-to-use admin UI for your data, served at /admin
  app.admin(db, [posts])
  app.listen(3000)
}
```

## The toolkit

### Generator

```bash
moonway generate scaffold Post title:string body:string
```

```console
created  models/post.mbt
created  handlers/posts.mbt
created  migrations/001_create_posts.mbt
updated  admin registry
```

> The `moonway` CLI ships with the framework — install it with
> `moon install daqing/moonway`.

### Admin dashboard

![Admin dashboard](docs/images/admin.png)

Every registered model gets a searchable, paginated CRUD interface at
`/admin` — create, edit, and inspect your data without writing a line of
frontend code.

### REPL console

```console
$ moonway console
moonway 0.1.0 · connected to sqlite://app.db
moonway> Post.count()
42
```

## Roadmap

moonway is being built in the open for the October 2026 MoonBit hackathon
(final submission: **October 31, 2026**).

- [x] Project setup — repository, toolchain, CI
- [x] HTTP router & middleware pipeline
- [x] SQLite database layer & migrations
- [x] Code generator (`moonway new`, `moonway generate`)
- [x] Redis cache middleware
- [x] WebSocket support
- [x] Auto-generated admin dashboard
- [x] REPL console
- [x] Preact frontend integration
- [x] Guides, examples & documentation

## Contributing

Contributions are welcome! Open an issue to talk about what you'd like to
build, or submit a pull request directly.

Before committing, install the git hooks (formatting and tests run on every
commit):

```bash
git config core.hooksPath .githooks
```

## License

[MIT](LICENSE) © 2026 David Zhang
