Skip to main content

Migrate from Laravel (PHP) to GoFr

Summary

Laravel devs moving to GoFr trade Eloquent and the Service Container for explicit SQL and constructor passing — and gain a static binary, built-in observability, and goroutine concurrency. Routes, controllers, middleware, validation, queues, and CLI commands all have direct GoFr analogues; the .env file even keeps its name.

Migrating with an AI assistant?

Hand https://gofr.dev/AGENTS.md to your coding assistant (Claude Code, Cursor, Codex, Aider). It contains the framework conventions, routing/binding/datasource patterns, and per-framework cheat-sheets so the assistant can translate handlers without you re-explaining GoFr.

Mental model

LaravelGoFr
Route::get('/users/{id}', ...)app.GET("/users/{id}", handler)
Controller methodHandler function (or method on a struct)
$request->input('name')var dto CreateUser; c.Bind(&dto)
Form Request validationStruct tags + a validator library
Middleware (Kernel.php)app.UseMiddleware(...)
Service Container / app()Constructor passing of dependencies
Eloquent ORMSQL drivers (c.SQL); pair with sqlc or gorm for ergonomics
Migrations (php artisan migrate)GoFr SQL migrations
Artisan commandsGoFr CLI / sub-commands
Queues (database/Redis/SQS)GoFr Pub/Sub (Kafka, NATS, SQS, MQTT, Google Pub/Sub, Azure Event Hub)
Scheduler (Kernel::schedule)app.AddCronJob(...)
.envconfigs/.env
Telescope / Horizon dashboardsPrometheus metrics + traces in your existing stack
Sanctum / PassportBuilt-in Basic / APIKey / OAuth-JWT + RBAC

Side-by-side: controller ↔ handler

Laravel:

php
class UserController extends Controller {
    public function store(Request $request) {
        $data = $request->validate([
            'name'  => 'required|min:3',
            'email' => 'required|email',
        ]);
        return User::create($data);
    }
}

Route::post('/users', [UserController::class, 'store']);

GoFr:

Go
type CreateUser struct {
    Name  string `json:"name"  validate:"required,min=3"`
    Email string `json:"email" validate:"required,email"`
}

app.POST("/users", func(c *gofr.Context) (any, error) {
    var dto CreateUser
    if err := c.Bind(&dto); err != nil {
        return nil, err
    }
    return createUser(c, dto)
})

Auto-CRUD via AddRESTHandlers

If your Laravel resource is "controller + Eloquent model + standard CRUD", you can collapse it in GoFr to:

Go
if err := app.AddRESTHandlers(&User{}); err != nil {
    app.Logger().Fatal(err)
}

— which exposes GET / POST / GET/{id} / PUT/{id} / DELETE/{id} against your struct/table. See the REST scaffolding guide.

Validation

Laravel's Form Requests collapse parsing + validating into one. In GoFr it's two steps:

  • c.Bind(&dto) — parse JSON / form / multipart.
  • A validator library (e.g. go-playground/validator) — apply struct-tag rules.

The trade-off is more explicit code, less magic.

Middleware

Laravel's Kernel.php middleware groups translate to:

Go
app.UseMiddleware(authMiddleware)
app.UseMiddleware(rateLimiter)

Authentication options ship in GoFr (Basic, API Key, OAuth-JWT — see authentication) and you can layer RBAC on top.

Eloquent → SQL drivers

This is the biggest shift. GoFr does not include an ORM. Replace Eloquent calls with explicit SQL via c.SQL.Query / Exec, and pair with sqlc for generated type-safe queries or gorm for ORM-like ergonomics.

Migrations move from php artisan make:migration to versioned GoFr SQL migrations — files applied in order at boot.

Queues → Pub/Sub

Laravel queues backed by Redis / database / SQS map to GoFr's Pub/Sub:

Go
app.Subscribe("user.created", func(c *gofr.Context) error {
    var msg UserCreated
    if err := c.Bind(&msg); err != nil {
        return err
    }
    return process(c, msg)
})

Supported backends: Kafka, NATS, SQS, MQTT, Google Pub/Sub, Azure Event Hub. Publish from inside a handler — GetPublisher is on *gofr.Context, and the payload must be []byte:

Go
func handler(c *gofr.Context) (any, error) {
    if err := c.GetPublisher().Publish(c, "user.created", []byte(`{"id":"1"}`)); err != nil {
        return nil, err
    }
    return map[string]string{"status": "queued"}, nil
}

Artisan → GoFr CLI

Laravel's Artisan commands (cleanup jobs, data backfills, one-off scripts) map onto GoFr's CLI / sub-command support — register sub-commands on the same app and invoke as ./mybinary <subcommand>. See the CLI command guide.

For periodic work, use app.AddCronJob(schedule, jobName, fn) (three arguments) instead of php artisan schedule:run, e.g. app.AddCronJob("0 * * * *", "hourly-cleanup", func(ctx *gofr.Context) { /* ... */ }).

Datasources

GoFr auto-initializes SQL and Redis from environment variables — set DB_DIALECT, DB_HOST, DB_PORT, DB_USER, DB_PASSWORD, DB_NAME (or REDIS_HOST, REDIS_PORT) in configs/.env and gofr.New() wires the connection. Other clients are registered explicitly with a provider:

Go
app.AddMongo(mongo.New(mongo.Config{/* ... */}))

SQL (MySQL/Postgres/Oracle/SQLite/SQL Server), Redis, Mongo, Cassandra, ScyllaDB, Couchbase, ArangoDB, Dgraph, SurrealDB are supported. File storage drivers cover Local, S3, GCS, Azure Blob, FTP, SFTP — useful when porting Laravel filesystem disks.

Configuration

.env — same name, slightly different conventions. GoFr reads configs/.env, with environment-specific files (configs/.env.production) layered on via APP_ENV. Read in code with app.Config.Get(key).

Observability

Telescope and Horizon are application-bundled dashboards; GoFr instead exports OpenTelemetry traces and Prometheus metrics at /metrics to whatever stack you already run (Grafana, Datadog, Honeycomb, etc.). Structured JSON logs include trace IDs. Health is exposed at /.well-known/health. Log levels are changeable at runtime.

Gradual adoption

Pick a bounded context (notifications, search, file processing) and rebuild it as a GoFr service. From Laravel call it over HTTP; from GoFr call back into Laravel with app.AddHTTPService("laravel-api", baseURL) — circuit breaker, retries, and rate limiting included.

Frequently asked