Migrate from Rails (Ruby) to GoFr
Summary
Rails and GoFr both lean opinionated, but Rails is opinionated about full-stack web apps and GoFr is opinionated about microservices. The mapping is operational rather than line-for-line: controllers become handler functions, ActiveRecord becomes explicit SQL, Active Job becomes Pub/Sub, Action Cable becomes WebSocket — and a lot of Rails magic ("convention over configuration") disappears in favor of explicit Go code with sensible framework defaults.
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
Rails earns its reputation by making the common case very short — a scaffold produces routes, controller, model, views, migrations, tests in one command. GoFr is opinionated in the same spirit but for a different shape of system: you are not building a server-rendered web app, you are building one of many small services that talk over HTTP / gRPC / Pub/Sub. The routing surface is smaller, the controllers are simpler, and the framework leans hard on built-in observability and resilience instead of view rendering.
| Rails | GoFr |
|---|---|
routes.rb | app.GET/POST/... calls in main |
| Controller action | Handler function (or method on struct) |
params[:id] | c.PathParam("id") |
params[:q] | c.Param("q") |
| Strong Parameters | c.Bind(&dto) into a typed struct |
| ActiveRecord | SQL drivers (c.SQL); pair with sqlc or gorm for ergonomics |
Migrations (db/migrate/*) | GoFr SQL migrations |
| Concerns / before_action | Middleware / composition |
| Active Job (Sidekiq, etc.) | GoFr Pub/Sub subscribers |
| Action Cable | GoFr WebSocket |
| Action Mailer | A mail library called from a handler or subscriber |
rails console | No direct equivalent — write a CLI sub-command for one-off tasks |
config/database.yml + secrets.yml | configs/.env (+ per-env files) |
| Puma workers | One Go binary, goroutine-per-request |
Side-by-side: controller ↔ handler
Rails:
class UsersController < ApplicationController
def create
user = User.create!(user_params)
render json: user, status: :created
end
private
def user_params
params.require(:user).permit(:name, :email)
end
end
GoFr:
type CreateUser struct {
Name string `json:"name"`
Email string `json:"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
For the "scaffold User name:string email:string" case, GoFr offers:
if err := app.AddRESTHandlers(&User{}); err != nil { // GET, POST, GET/{id}, PUT/{id}, DELETE/{id}
app.Logger().Fatal(err)
}
See the REST scaffolding guide. For non-CRUD actions (Rails' member / collection routes), write a plain handler.
ActiveRecord → SQL drivers
The biggest mental shift. GoFr does not include an ORM. Replace ActiveRecord calls with explicit SQL via c.SQL.Query / Exec, optionally generated by sqlc. Lazy associations, scopes, and includes(:posts) are not implicit — write the JOIN, or two queries, deliberately.
Migrations move from db/migrate/2024..._create_users.rb to versioned GoFr SQL migrations — files applied in order at boot. SQL (MySQL/Postgres/Oracle/SQLite/SQL Server), MongoDB, Redis, Cassandra, ScyllaDB, Couchbase, ArangoDB, Dgraph, SurrealDB are supported.
Active Job → Pub/Sub
Rails background jobs (Sidekiq / Resque / GoodJob) map to GoFr Pub/Sub subscribers:
app.Subscribe("user.welcome", func(c *gofr.Context) error {
var msg WelcomeJob
if err := c.Bind(&msg); err != nil {
return err
}
return sendWelcome(c, msg)
})
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.welcome", []byte(`{"id":"1"}`)); err != nil {
return nil, err
}
return map[string]string{"status": "queued"}, nil
}
For periodic jobs (cron-style), use app.AddCronJob(schedule, jobName, fn) — three arguments, e.g. app.AddCronJob("0 * * * *", "hourly-report", reportFn).
Action Cable → WebSocket
GoFr supports WebSocket directly — register a WS handler on the app, manage connections, broadcast through your own routing or via Pub/Sub fan-out.
Concerns and before_action
Rails' before_action hooks and Concerns translate to two patterns:
- Cross-cutting (auth, rate limiting, logging) —
app.UseMiddleware(...). - Per-handler — wrap the handler, or add a small helper called at the top of each handler.
Authentication options (Basic, API Key, OAuth-JWT) and RBAC are built in.
Configuration
config/database.yml, secrets.yml, and Rails credentials → configs/.env, with configs/.env.production etc. layered on via APP_ENV. Read with app.Config.Get(key).
CLI tasks (rake, generators)
There is no Rails console, but for one-off tasks (data backfills, admin actions) GoFr supports CLI sub-commands on the same binary — see the CLI command guide. Register a sub-command and invoke as ./mybinary <subcommand>.
Observability
Rails teams typically wire prometheus-client, opentelemetry-instrumentation-rails, and a logger by hand. GoFr emits OpenTelemetry traces, Prometheus metrics at /metrics, structured JSON logs (with trace IDs), and /.well-known/health automatically. Log levels can be changed at runtime via the remote log-level endpoint.
Gradual adoption
Pick a bounded context — webhook receiver, notification service, search — and rebuild it in GoFr. Run alongside Rails, route via your gateway. From GoFr call back into Rails with app.AddHTTPService("rails", baseURL) — circuit breaker, retries, and rate limiting included. Move endpoints across one bounded context at a time.

