Skip to content

iammikek/gebweb-101

Repository files navigation

Getting Fast at Gebweb

A step-by-step guide to a minimal Gebweb API for Laravel developers reskilling onto Geblang. Same items/categories JSON contract as laravel-101 and fastAPI-101: JWT auth, SQLite, pagination, filters, stats, and feature tests — API-only (no /shop UI).

Audience: Laravel / PHP developers who know controllers, Form Requests, Eloquent, Sanctum, and Pest/PHPUnit, and want the same mental model on Gebweb (decorator-driven MVC on a Geblang/Go runtime).

Laravel idea Gebweb / this repo
Controllers + route files @Controller / @Get / @Post / @Patch / @Delete
auth:sanctum middleware @Auth + gebweb.useAuthenticator
Form Requests DTOs + validators.gb (Laravel-style messages)
Eloquent / repositories db.Connection + src/services/
paginate() { items, total, skip, limit }
PHPUnit / Pest geblang test + gebweb.TestClient
/docs (Scribe / OpenAPI) Gebweb SwaggerUI at /docs

Related: geblang-101 is the same API with Geblang’s stdlib web.router only (no Gebweb). This repo is the framework path. Full catalogue: § *-101 Family.

By the end you can start the API with one command, authorize in /docs, and map every folder back to a Laravel concept.


What's Included

  1. Gebweb 1.8.4 on Geblang 1.32 REST API on port 8014
  2. SQLite via db.Connection (foreign keys on)
  3. JWT auth — register, login (form-urlencoded username/password), Bearer on writes + /auth/me
  4. Controllers under src/controllers/ (Laravel-style route groups)
  5. Service layer — user, category, item modules under src/services/
  6. Domain errors with { detail, code } responses (familiar from laravel-101 / fastAPI-101)
  7. Pagination{ items, total, skip, limit } plus item filters
  8. StatsGET /items/stats/summary with per-category breakdown
  9. 18 feature tests via geblang test + gebweb.TestClient (no live HTTP server)
  10. Dockerfile (dwgebler/geblang), docker-compose, GitHub Actions CI, Makefile
  11. OpenAPI 3.1 + SwaggerUI mounted at /docs and /openapi.json

Table of Contents

  1. Quick Start
  2. Project Structure
  3. Dependencies: geblang.yaml
  4. Controllers (Laravel Controllers)
  5. Services (thin controllers)
  6. Validation (Form Requests)
  7. JWT authentication (Sanctum)
  8. Database & schema
  9. Feature tests (PHPUnit / Pest)
  10. Docker & CI
  11. Laravel → Gebweb Mapping
  12. Geblang notes for Laravel folks
  13. Quick Reference
  14. *-101 Family

Quick Start

Pick one: Docker or local Geblang — both use port 8014, so don’t run them at the same time.

Option A: Docker (recommended if you don’t have Geblang installed)

cp .env.example .env   # optional
docker compose up --build

Option B: Local Geblang

Install a Geblang release and put geblang on your PATH, then:

cp .env.example .env
make serve

make serve runs geblang install (pulls Gebweb into vendor/ from geblang.yaml) then starts the API.

Or without Compose:

make serve-docker

Tests

make test
# or without a local install:
make test-docker

Then open:

Note: Local and Docker both use SQLite by default (DB_DATABASE). See .env.example for variables.


Project Structure

gebweb-101/
├── src/
│   ├── main.gb                 # Entry: migrate + gebweb.serve (like public/index.php + artisan serve)
│   ├── app.gb                  # App factory: controllers, CORS, JWT authenticator
│   ├── settings.gb             # Env config (like config/*.php / .env)
│   ├── domain.gb               # DomainError + {detail, code} helper
│   ├── database.gb             # SQLite connection + migrate / reset
│   ├── security.gb             # passwordHash + JWT + CurrentUser
│   ├── validators.gb           # Laravel-style validation messages
│   ├── serializers.gb          # API Resources–style JSON shaping
│   ├── controllers/            # Route controllers (like app/Http/Controllers)
│   │   ├── health.gb
│   │   ├── auth.gb
│   │   ├── categories.gb
│   │   └── items.gb
│   ├── services/               # Business logic (like app/Services)
│   │   ├── user.gb
│   │   ├── category.gb
│   │   └── item.gb
│   └── support/testkit.gb      # TestClient helpers (like tests/TestCase)
├── database/schema.sql         # Reference schema
├── tests/feature/              # Feature tests (18)
├── geblang.yaml                # Package manifest + Gebweb dependency
├── geblang.lock                # Pinned Gebweb commit
├── Dockerfile
├── docker-compose.yml
├── Makefile
├── .env.example
└── .github/workflows/ci.yml

Dependencies: geblang.yaml

What it is: The package manifest — Geblang’s analogue of composer.json.

What we declare:

name: gebweb-101
version: 0.1.0
source: src
dependencies:
  gebweb:
    git: https://github.com/dwgebler/gebweb
    version: "1.8.4"
Laravel / Composer This project
composer.json geblang.yaml
composer.lock geblang.lock
vendor/ vendor/ (gitignored; created by geblang install)
composer install geblang install / make install

Key idea: Anyone (or Docker) can run geblang install and get the same Gebweb commit pinned in geblang.lock.


Controllers (Laravel Controllers)

What they are: Classes with HTTP methods. Gebweb discovers routes from decorators — closer to Symfony attributes or NestJS than Laravel’s routes/api.php + controller methods, but the job is the same: thin HTTP adapters.

Laravel Gebweb
Route::get('/items', ...) @Get("") on @Controller("/items")
Route::post(...) @Post("")
Path {item} {id} path params bind by name
Controller __invoke / actions Methods returning http.Response via this.json(...)

Example (health — like a trivial Laravel controller):

@Controller("")
export class HealthController extends gebweb.Controller {
    @Get("/")
    func root(): http.Response {
        return this.json({
            "message": "Hello from gebweb-101",
            "docs": "https://github.com/dwgebler/gebweb",
            "openapi": "/docs"
        });
    }

    @Get("/health")
    func health(): http.Response {
        return this.json({"status": "ok"});
    }
}

App wiring (src/app.gb) registers controllers, OpenAPI info, JWT authenticator, and CORS — think RouteServiceProvider + middleware groups in one place:

let app = gebweb.app([
    HealthController(),
    AuthController(),
    CategoriesController(),
    ItemsController(),
]);
gebweb.useAuthenticator(app, CurrentUser, ...);
gebweb.use(app, gebweb.cors({ ... }));

Services (thin controllers)

What they are: Business logic outside HTTP — Laravel app/Services or fat models extracted into services.

Concern Lives in
HTTP status, JSON shape, @Auth src/controllers/
SQL, uniqueness, “category in use” src/services/
{ detail, code } mapping src/domain.gb

Controllers catch domain.DomainError and return the shared *-101 error shape:

{ "detail": "Item not found", "code": "ITEM_NOT_FOUND" }

Same contract as laravel-101 / fastAPI-101 so SPA clients (react-101, etc.) can swap backends by port.


Validation (Form Requests)

Laravel: $request->validate([...]) or Form Request classes with messages like “The name field is required.”

gebweb-101: validators.gb uses the same message style. Controllers call validators.validateOrRaise(data, rules) before hitting services. Failures become 422 with { "detail": "..." }.

Laravel rule Example here
required|email|min:5 Register email
required|string|min:8 Password
numeric|gt:0 Item price
integer|min:1|max:100 limit query param

Gebweb can also validate DTOs with @Assert.* decorators; this repo keeps Laravel-style messages for parity with other *-101 backends.


JWT authentication (Sanctum)

This is the Sanctum / auth:api story: register a user, exchange credentials for a Bearer JWT, protect writes.

Laravel gebweb-101
User + Hash::make() users table + crypt.passwordHash
Auth::attempt() services.user.authenticate
Sanctum / jwt-auth HS256 JWT (JWT_SECRET)
Authorization: Bearer {token} Same header
auth:sanctum on route group @Auth on controller methods
$request->user() CurrentUser injected into the handler

Important Gebweb detail: import the user class with a selective import so the type name matches the authenticator:

from security import CurrentUser;

@Post("")
@Auth
func create(CurrentUser user, CategoryCreateDTO body): http.Response { ... }

(security.CurrentUser as a qualified parameter type is treated as a body DTO — use from security import CurrentUser.)

Try it in Swagger

  1. Open http://localhost:8014/docs
  2. POST /auth/register{ "email": "you@example.com", "password": "password123" }
  3. POST /auth/loginusername = email, password = your password (form fields, like OAuth2 password grant)
  4. Copy access_token
  5. Click AuthorizeBearer <token>
  6. Try POST /items or POST /categories

curl:

# Register
curl -X POST http://localhost:8014/auth/register \
  -H "Content-Type: application/json" \
  -d '{"email":"you@example.com","password":"password123"}'

# Login (form-urlencoded; username = email)
curl -X POST http://localhost:8014/auth/login \
  -d "username=you@example.com&password=password123"

# Create item with token
curl -X POST http://localhost:8014/items \
  -H "Authorization: Bearer YOUR_TOKEN_HERE" \
  -H "Content-Type: application/json" \
  -d '{"name":"Widget","price":9.99}'

GET list/show routes stay public; POST/PATCH/DELETE and GET /auth/me require JWT.


Database & schema

Laravel: migrations + Eloquent.
gebweb-101: database.migrate() in src/database.gb creates tables on startup (fine for a learning SQLite app). Reference DDL also lives in database/schema.sql.

Table Role
users Auth (email + password hash)
categories Unique name; delete blocked if items reference it (409 CATEGORY_IN_USE)
items price as text/decimal; optional category_id FK

List filters on GET /items (Laravel query scopes / when()):

Param Example
skip / limit ?skip=0&limit=10
min_price / max_price ?min_price=10&max_price=25
category_id ?category_id=2
name_contains ?name_contains=widget

Pagination envelope (Laravel paginate() simplified):

{
  "items": [ ... ],
  "total": 42,
  "skip": 0,
  "limit": 10
}

Feature tests (PHPUnit / Pest)

What you'll use:

Tool Purpose
geblang test Discovers @test methods on classes extending test.Test
gebweb.TestClient In-process HTTP (like Laravel Http:: / $this->getJson) — no port bind
support/testkit.gb Fresh :memory: DB + authToken() helper per test

Run:

make test
# geblang test tests/ -v

18 tests cover health, auth, categories, item list/pagination/validation, create/delete, stats, and filters — same behavioural surface as flask-101 / geblang-101.

Example pattern:

class AuthTest extends test.Test {
    @test
    func meRequiresAuth(): void {
        let client = h.setupClient();
        client.get("/auth/me").assertStatus(401);
        string token = h.authToken(client);
        h.getAuth(client, "/auth/me", token).assertStatus(200);
    }
}

Docker & CI

Piece Role
Dockerfile FROM dwgebler/geblang:1.32.0, geblang install, serve src/main.gb
docker-compose.yml Port 8014, SQLite volume
.github/workflows/ci.yml geblang installgeblang checkgeblang test; separate Docker build job

Laravel parallel: Forge/Envoyer aside, this is “Dockerfile + GitHub Actions green on every push,” same idea as laravel-101’s CI.


Laravel → Gebweb Mapping

Laravel gebweb-101
artisan serve / Octane gebweb.serve / make serve
routes/api.php + Controllers @Controller + method decorators
middleware('auth:sanctum') @Auth
$request->user() CurrentUser parameter
Form Requests DTOs + validators.gb
API Resources serializers.gb
Eloquent db.Connection + services
paginate() { items, total, skip, limit }
abort(404) / custom exceptions domain.*Error{ detail, code }
Hash / JWT package crypt.passwordHash / crypt.jwtSign
PHPUnit / Pest feature tests geblang test + TestClient
composer.json geblang.yaml
Scribe / OpenAPI /docs + /openapi.json
.env .env / settings.gb (PORT, JWT_SECRET, …)

Geblang notes for Laravel folks

  • Comments use #, not // — in Geblang // is integer division.
  • Modules named config / errors collide with stdlib — this app uses settings and domain.
  • :memory: SQLite uses a pooled connection: close query cursors (rows.close() / defer) before the next query, or list endpoints can deadlock.
  • Prefer from security import CurrentUser for @Auth injection (see JWT).
  • Sibling without the framework: geblang-101 (port 8013, stdlib router only).

Quick Reference

Goal Command
Copy env cp .env.example .env
Install Gebweb make install / geblang install
Start app (Docker) docker compose up --build
Start app (local) make servehttp://127.0.0.1:8014
Start via Docker image only make serve-docker
Stop Docker docker compose down
Port 8014 in use Stop the other process / compose stack
Run tests make test / make test-docker
Static check make check
View CI runs GitHub → Actions
Interactive docs http://localhost:8014/docs

*-101 Family

API backends

Repo Port Type Stack
fastAPI-101 8000 API-only FastAPI, SQLAlchemy
django-101 8001 Monolith Django + DRF + shop
symfony-101 8002 Monolith Symfony + shop
laravel-101 8003 Monolith Laravel + shop
framework-x-101 8004 Monolith Framework X + shop
orchestr-101 8005 Monolith Orchestr + shop
nest-101 8006 API-only NestJS, TypeScript
express-101 8007 API-only Express, Vitest
go-101 8000* API-only Gin, GORM
fortran-101 8008 API-only Fortran, fpm
java-101 8009 API-only Spring Boot, JPA, Flyway
dotNet-101 8010 API-only ASP.NET Core, xUnit
flask-101 8011 API-only Flask, pytest
rails-101 8012 Monolith Rails + shop
geblang-101 8013 API-only Geblang stdlib router
gebweb-101 8014 API-only Geblang + Gebweb
sinatra-101 8015 API-only Sinatra, RSpec
* go-101 also uses port 8000 — run one backend at a time, or change port in config.

Other clients

Repo Platform Stack
flutter-101 Mobile / desktop Flutter (iOS, macOS, Android)
react-101 Web browser React 19, Vite, Vitest
vue-101 Web browser Vue 3, Vite, Pinia
alpine-101 Web browser Alpine.js, Vite, Vitest

Suggested pairing

Catalogue: automica.io/learning-101

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages