Flask for Rails Developers A Gentle Introduction

Part 1 of a series on making the transition from Ruby on Rails to Flask

If you've spent years in the warm embrace of Rails—where convention rules, generators abound, and rails new gives you everything but the kitchen sink, stepping into Flask can feel like walking into an empty room. Where's the furniture? Where are the folders? Why is my entire app in one file?

Don't panic. That emptiness is the point.

The Philosophy Shift

Rails is famously opinionated. It makes a thousand decisions for you: where your models live, how your database migrations work, which testing framework you'll use, how assets get compiled. This is wonderful when you want to move fast and follow well-trodden paths.

Flask takes the opposite approach. It gives you a micro framework, routing, request/response handling, templates and then gets out of your way. Need an ORM? Pick one. Want background jobs? Choose your own adventure. Database migrations? Here are five options.

This isn't better or worse than Rails. It's different. And once you understand the difference, you can actually start to appreciate what each approach offers.

Your First "Hello World" Moment

In Rails, getting to hello world involves generating an app, creating a controller, defining a route, and writing a view. In Flask:

from flask import Flask

app = Flask(__name__)

@app.route('/')
def hello():
    return 'Hello, World!'

if __name__ == '__main__':
    app.run(debug=True)

That's it. Save it as app.py, run python app.py, and you've got a web server. If you're coming from Rails, this might feel too simple, but it's genuinely all you need to start.

Mental Model Mapping: Rails → Flask

Let's translate some familiar Rails concepts:

Routing

In Rails, you've got config/routes.rb with its elegant DSL:

# Rails
get '/users/:id', to: 'users#show'

In Flask, routes are decorators attached directly to functions:

# Flask
@app.route('/users/<int:id>')
def show_user(id):
    return f'User {id}'

The route and the handler live together. No hunting through multiple files to trace a request. Some find this cleaner; others miss the centralized routing file. You can actually create a centralized approach in Flask too using Blueprints, but that's a topic for a later post.

Controllers → View Functions

Rails controllers are classes with action methods. Flask has "view functions", plain functions decorated with routes. There's no implicit rendering, no instance variables magically available in templates. You explicitly return responses.

from flask import render_template

@app.route('/users/<int:id>')
def show_user(id):
    user = get_user(id)  # You'll fetch this however you want
    return render_template('user.html', user=user)

Notice you pass user explicitly to the template. No @user instance variable magic.

Views → Templates (Jinja2)

Flask uses Jinja2 for templating, and honestly, it'll feel familiar. ERB and Jinja2 are cousins:

<%# Rails ERB %>
<h1><%= @user.name %></h1>
<% @user.posts.each do |post| %>
  <p><%= post.title %></p>
<% end %>
{# Flask Jinja2 #}
<h1>{{ user.name }}</h1>
{% for post in user.posts %}
  <p>{{ post.title }}</p>
{% endfor %}

Different delimiters, same idea. You'll adjust in about ten minutes.

ActiveRecord → SQLAlchemy (or whatever you want)

Here's where things diverge significantly. Rails gives you ActiveRecord, period. Flask gives you... nothing. You bring your own ORM.

Most Flask developers use SQLAlchemy, often via the Flask-SQLAlchemy extension. It's powerful, flexible, and has a steeper learning curve than ActiveRecord. Where ActiveRecord emphasises convention and simplicity, SQLAlchemy gives you fine-grained control over everything.

A quick taste:

from flask_sqlalchemy import SQLAlchemy

db = SQLAlchemy()

class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(100), nullable=False)
    email = db.Column(db.String(120), unique=True, nullable=False)
    
    def __repr__(self):
        return f'<User {self.name}>'

No migrations generated automatically. No belongs_to or has_many magic syntax (though relationships exist—they're just more explicit). You'll need Flask-Migrate for database migrations, which wraps Alembic.

We'll dive deep into SQLAlchemy in a future post. For now, just know it exists and it's different.

What You'll Miss (At First)

Let's be honest about the adjustment period:

The generators. No rails generate model User name:string email:string. You're writing boilerplate yourself, at least until you build your own tooling or find libraries that help.

Convention over configuration. Flask won't yell at you for putting files in the "wrong" place because there is no wrong place. This freedom can feel paralyzing initially.

The ecosystem coherence. Rails gems tend to work together seamlessly. Flask extensions are more independent, sometimes they integrate beautifully, sometimes you're reading documentation trying to figure out how to make two libraries play nice.

Asset pipeline. Flask doesn't have one. You'll use external tools like Vite, Webpack, or just serve static files directly.

What You'll Love (Eventually)

Simplicity. When your Flask app breaks, you can usually read the entire codebase and understand what's happening. There's less "magic" to debug.

Flexibility. Want to use a different ORM? Go for it. Prefer a different templating engine? Swap it. Flask doesn't care.

Explicit is better than implicit. This is a core Python philosophy, and Flask embraces it. When you see code, you understand what it does without knowing framework conventions.

Fast startup. Small Flask apps start instantly. No waiting for Rails to boot.

Learning. Building things from scratch teaches you how web frameworks actually work. You'll understand HTTP better, appreciate what Rails does for you more, and become a better developer overall.

A Real World Comparison

Let's build a tiny feature: a JSON endpoint that returns a user's data.

Rails:

# config/routes.rb
get '/api/users/:id', to: 'api/users#show'

# app/controllers/api/users_controller.rb
class Api::UsersController < ApplicationController
  def show
    user = User.find(params[:id])
    render json: user
  end
end

Flask:

from flask import Flask, jsonify

app = Flask(__name__)

@app.route('/api/users/<int:id>')
def get_user(id):
    user = User.query.get_or_404(id)
    return jsonify(id=user.id, name=user.name, email=user.email)

Rails is more structured, separate files for routes and controllers, namespaced organisation. Flask is more direct, route and logic together, less ceremony.

Neither is wrong. They're optimised for different things. And here is what I always say, we should use the best tool for the job at hand. Like Rails, Flask is another tool in your toolbox.

Setting Up Your First Real Flask Project

Here's what I suggest for Rails developers starting a Flask project:

# Create a project directory
mkdir my_flask_app
cd my_flask_app

# Create a virtual environment (like bundler, but for Python)
python -m venv venv
source venv/bin/activate  # On Windows: venv\Scripts\activate

# Install Flask
pip install flask

# Create your app
touch app.py

or better yet, use Poetry - which is a topic for another post

Start with everything in app.py. Yes, really. Fight the urge to create a folder structure immediately. Let the complexity emerge naturally, then refactor.

What's Next

In upcoming posts, we'll cover:

  • Part 2: Project structure, when and how to break out of a single file
  • Part 3: SQLAlchemy deep dive for ActiveRecord users
  • Part 4: Flask-Migrate and database migrations
  • Part 5: Testing in Flask (it's actually quite pleasant)
  • Part 6: Authentication without Devise
  • Part 7: Background jobs and async processing
  • Part 8: Deploying Flask apps

Closing Thoughts

Switching from Rails to Flask isn't about finding a "better" framework. It's about expanding your toolkit and understanding different approaches to the same problems. Rails will always be there when you need its power and conventions. Flask will be there when you want simplicity and control.

The best part? Learning Flask will make you a better Rails developer too. You'll understand what Rails does for you, why it makes certain choices, and when those choices might not fit your needs.

Start small. Build something. Break it. Fix it. It's all part of the process.

Have questions about Flask from a Rails perspective? Topics you'd like me to cover? Drop a comment below or reach out on Twitter.

Looking for a fractional Rails engineer or CTO?

I take on a limited number of part-time clients.

Get in touch