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
| Laravel | GoFr |
|---|---|
Route::get('/users/{id}', ...) | app.GET("/users/{id}", handler) |
| Controller method | Handler function (or method on a struct) |
$request->input('name') | var dto CreateUser; c.Bind(&dto) |
| Form Request validation | Struct tags + a validator library |
Middleware (Kernel.php) | app.UseMiddleware(...) |
Service Container / app() | Constructor passing of dependencies |
| Eloquent ORM | SQL drivers (c.SQL); pair with sqlc or gorm for ergonomics |
Migrations (php artisan migrate) | GoFr SQL migrations |
| Artisan commands | GoFr 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(...) |
.env | configs/.env |
| Telescope / Horizon dashboards | Prometheus metrics + traces in your existing stack |
| Sanctum / Passport | Built-in Basic / APIKey / OAuth-JWT + RBAC |
Side-by-side: controller ↔ handler
Laravel:
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:
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:
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:
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:
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:
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:
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.

