What's included
Dual-mode Auth
Session-based login for the web UI. JWT Bearer tokens for the REST API. First registered user gets the admin role.
CRM
Client records with status tracking (lead / active / inactive), company, and notes. Full CRUD via both web UI and API.
CMS
Pages with title, slug, Markdown content, and a published toggle. Slug uniqueness enforced at the DB level.
REST API
JSON endpoints under /api/v1/* protected with JWT Bearer. Token auth via /api/auth/*.
Zero-config SQLite
Runs with no database server. WASM-based SQLite driver — no CGO. Swap to Postgres or MySQL by changing two files.
Hot reload
Air configured out of the box. Run air and edits rebuild instantly.
Quick start
Requires Go 1.22+. No database server needed.
# Clone and install
git clone https://github.com/alexramsey92/goravel-test.git
cd goravel-test
go mod tidy
# Set up environment
cp .env.example .env
go run . artisan key:generate
go run . artisan jwt:secret
# Migrate and run
go run . artisan migrate
go run .
Open http://localhost:3000 and register your first account — it automatically receives the admin role.
REST API
| Method | Path | Auth |
|---|---|---|
| POST | /api/auth/register | — |
| POST | /api/auth/login | — |
| GET | /api/v1/me | Bearer |
| GET | /api/v1/clients | Bearer |
| POST | /api/v1/clients | Bearer |
| GET | /api/v1/clients/:id | Bearer |
| DELETE | /api/v1/clients/:id | Bearer |
| GET | /api/v1/pages | Bearer |
| POST | /api/v1/pages | Bearer |
# Login and capture token
TOKEN=$(curl -s -X POST http://localhost:3000/api/auth/login \
-d "email=you@example.com&password=secret" | jq -r .token)
# Fetch current user
curl -H "Authorization: Bearer $TOKEN" http://localhost:3000/api/v1/me
# List clients
curl -H "Authorization: Bearer $TOKEN" http://localhost:3000/api/v1/clients
Laravel → Goravel
Built by a Laravel developer. Here's how the concepts translate.
| Laravel | Goravel |
|---|---|
| Auth::user() | facades.Auth(ctx).User(&user) |
| Hash::make($pw) | facades.Hash().Make(pw) |
| Hash::check($pw, $hash) | facades.Hash().Check(pw, hash) // bool only |
| Model::query()->where() | facades.Orm().Query().Where() |
| ->count() | ->Count() // (int64, error) |
| redirect('/dashboard') | ctx.Response().Redirect(302, "/dashboard") |
| view('clients.index', [...]) | ctx.Response().View().Make("clients/index.tmpl", map[string]any{...}) |
| php artisan migrate | go run . artisan migrate |
Gotchas
Template naming
Every .tmpl file must be wrapped in
{{define "path/name.tmpl"}}...{{end}}.
Go's ParseFiles() names templates by base filename only —
without the define wrapper, files with the same name (like index.tmpl) silently overwrite each other.
Session middleware
You must apply sessionmiddleware.StartSession() to all web routes
or facades.Auth(ctx).Login() will panic with
"session driver is not set". It's the equivalent of Laravel's web middleware group.
GroupFunc signature
Route groups take func(router route.Router), not func().
The router parameter is how you register routes inside the group.
Middleware type
Middleware() takes func(http.Context), not strings like "auth:api".
You must define a middleware struct and pass its .Handle method.
See the full source
All code is on GitHub — including the README with the complete Laravel→Goravel mapping table, database switching guide, and project structure overview.
alexramsey92/goravel-test