Build a Web App with Flask in 5 Steps

Build a Web App with Flask in 5 Steps

Build and deploy a Flask web app in 5 steps: routes, database, templates, config, production server. Real code for solo founders shipping MVPs.

Flask is a Python microframework. It's simple, powerful, and gets you a working web app fast—in under an hour, to be precise. You write routes, connect a database, and deploy. Here's the thing: Flask doesn't impose a specific folder structure or use an ORM. It's all about Python functions returning HTML or JSON. This guide will help you create a minimal task manager, database included.

A MacBook with lines of code on its screen on a busy desk Photo: Christopher Gower on Unsplash

Who this is for: Solo founders crafting MVPs, developers wanting quick cycles without Django's bulk, or anyone aiming for internal tools on a Linux VPS minus serverless headaches.

Step 1: Install Flask and Set Up Your Project

First, create a directory and set up a Python virtual environment. Flask doesn't dictate structure, but isolation is crucial.

mkdir flask-taskapp && cd flask-taskapp
python3 -m venv venv
source venv/bin/activate  # On Windows: venv\Scripts\activate
pip install Flask

Create app.py in the root:

from flask import Flask

app = Flask(__name__)

@app.route('/')
def index():
    return "Task app running"

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

Run it:

python app.py

Visit http://127.0.0.1:5000/ in your browser. You should see "Task app running." That's your web server—one file, zero config.

Worth noting, Flask uses Werkzeug for WSGI and Jinja2 for templates. The official Flask documentation is comprehensive, maintained by the Pallets team and spearheaded by Armin Ronacher since 2010.

Step 2: Add a Database with SQLite and Flask-SQLAlchemy

silver iMac turned on inside room Photo: Lee Campbell on Unsplash

Many solopreneur apps start with SQLite. It's just a single file—no daemon, no ports. Migration is always an option if needed.

Install Flask-SQLAlchemy:

pip install Flask-SQLAlchemy

Update app.py:

from flask import Flask, render_template, request, redirect, url_for
from flask_sqlalchemy import SQLAlchemy

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///tasks.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app)

class Task(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    content = db.Column(db.String(200), nullable=False)
    completed = db.Column(db.Boolean, default=False)

with app.app_context():
    db.create_all()

@app.route('/')
def index():
    tasks = Task.query.all()
    return render_template('index.html', tasks=tasks)

@app.route('/add', methods=['POST'])
def add_task():
    content = request.form.get('content')
    if content:
        new_task = Task(content=content)
        db.session.add(new_task)
        db.session.commit()
    return redirect(url_for('index'))

@app.route('/delete/<int:task_id>')
def delete_task(task_id):
    task = Task.query.get_or_404(task_id)
    db.session.delete(task)
    db.session.commit()
    return redirect(url_for('index'))

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

Here’s what happens: a Task model with fields id, content, and completed is defined. db.create_all() creates the SQLite file on the first run. Routes handle listing tasks, adding new ones, and deleting by ID.

SQLAlchemy abstracts SQL, though raw queries are possible for performance. In practice, the ORM suffices for solo apps under 10,000 rows.

Step 3: Build Templates with Jinja2

Flask employs Jinja2 for templating. Create a templates folder in your project root and add index.html:

mkdir templates

templates/index.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Task Manager</title>
    <style>
        body { font-family: sans-serif; max-width: 600px; margin: 50px auto; }
        input[type="text"] { width: 70%; padding: 8px; }
        button { padding: 8px 16px; }
        ul { list-style: none; padding: 0; }
        li { padding: 10px; border-bottom: 1px solid #ddd; }
        a { color: red; text-decoration: none; margin-left: 10px; }
    </style>
</head>
<body>
    <h1>Tasks</h1>
    <form action="/add" method="POST">
        <input type="text" name="content" placeholder="New task" required>
        <button type="submit">Add</button>
    </form>
    <ul>
        {% for task in tasks %}
        <li>
            {{ task.content }}
            <a href="/delete/{{ task.id }}">Delete</a>
        </li>
        {% endfor %}
    </ul>
</body>
</html>

Restart python app.py and reload your browser. You now have a task manager with add and delete capabilities. Jinja2 uses {% %} for logic, {{ }} for output.

Templates are in templates/ because Flask defaults to it. Honestly, for 90% of single-developer projects, the default is just fine.

Step 4: Add Environment Config and Secrets

Hardcoding database URIs and secret keys breaks deployment. Use environment variables and a .env file.

Install python-dotenv:

pip install python-dotenv

Create .env in the project root:

SECRET_KEY=your-random-secret-key-here
DATABASE_URI=sqlite:///tasks.db

Update app.py:

import os
from flask import Flask, render_template, request, redirect, url_for
from flask_sqlalchemy import SQLAlchemy
from dotenv import load_dotenv

load_dotenv()

app = Flask(__name__)
app.config['SECRET_KEY'] = os.getenv('SECRET_KEY')
app.config['SQLALCHEMY_DATABASE_URI'] = os.getenv('DATABASE_URI')
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app)

class Task(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    content = db.Column(db.String(200), nullable=False)
    completed = db.Column(db.Boolean, default=False)

with app.app_context():
    db.create_all()

@app.route('/')
def index():
    tasks = Task.query.all()
    return render_template('index.html', tasks=tasks)

@app.route('/add', methods=['POST'])
def add_task():
    content = request.form.get('content')
    if content:
        new_task = Task(content=content)
        db.session.add(new_task)
        db.session.commit()
    return redirect(url_for('index'))

@app.route('/delete/<int:task_id>')
def delete_task(task_id):
    task = Task.query.get_or_404(task_id)
    db.session.delete(task)
    db.session.commit()
    return redirect(url_for('index'))

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

Add .env to .gitignore to avoid committing secrets:

echo ".env" >> .gitignore

On production servers, set environment variables directly. Most platforms (Heroku, Render, DigitalOcean App Platform) offer env config in their dashboards. Remember, never commit .env to version control.

Step 5: Deploy to a Production Server

Flask's server is not for production—it's single-threaded and slow. Use Gunicorn as your WSGI server.

Install Gunicorn:

pip install gunicorn

Create requirements.txt:

pip freeze > requirements.txt

Your requirements.txt should list:

Flask==3.0.0
Flask-SQLAlchemy==3.1.1
gunicorn==21.2.0
python-dotenv==1.0.0

Test locally with Gunicorn:

gunicorn app:app

This runs a production-ready server at http://127.0.0.1:8000. The format module:variableapp is your file, the second app is the Flask instance.

For deployment, DigitalOcean droplets work well with a $6/month VPS. SSH in, clone your repo, install dependencies, and run Gunicorn behind Nginx. The DigitalOcean Flask deployment guide covers the complete setup.

Prefer platforms over servers? Render and Railway auto-detect Flask apps and deploy from a GitHub push. Both offer free tiers enough for MVPs under 1,000 daily users.

What Nobody Tells You About Flask

Flask does not scale your code—you do. It's minimal by design. You add session management, user auth, background jobs, and API versioning. Django offers all of it; Flask offers none, until extensions are pulled in.

Wondering about organizing code? Blueprints are Flask's answer. If app.py exceeds 300 lines, split routes into blueprints. Most single developers do fine with one file for months—don’t overthink it.

SQLite struggles with concurrent writes. If building multi-user SaaS expecting heavy writes, switch to PostgreSQL early. Flask-SQLAlchemy makes it a config change, but SQL patterns might not translate. Test queries on Postgres in local Docker first.

For session management, use Redis or Flask-Session for server-side sessions. Build anything with logins? Install Flask-Login. Creating a custom session layer is a pitfall.

Error handling in production is silent by default. Use Sentry or similar to catch exceptions. Flask’s debug mode shows stack traces locally—never use it in production.

FAQ

Is Flask faster than Django?

Flask isn't inherently faster—both are WSGI apps behind similar servers (Gunicorn, uWSGI). But Flask has less startup overhead, not loading Django's ORM, admin panel, or middleware stack. Once extensions add up, the performance gap narrows. Choose Flask for dependency control; choose Django for an all-in-one package.

Can Flask handle thousands of users?

Yes, with the right setup. Instagram ran on Flask before Facebook's acquisition. Usually, the database is your bottleneck, not Flask. Use connection pooling, query optimization, and Redis caching. Scale horizontally by deploying behind a load balancer with multiple Gunicorn workers. Remember, Flask is stateless.

Do I need Docker for Flask deployment?

No, but it aids consistency. Deploying to a VPS? Install Python and dependencies directly. Platforms like Render or Fly.io manage containers for you. Docker is handy for multi-services (Flask + Celery + Redis) or matching dev and production environments. For solo MVPs, a requirements.txt and systemd service file suffice.

What's the best database for Flask?

Start with SQLite for projects under 10,000 rows. Switch to PostgreSQL for concurrent writes, full-text search, or JSON querying. Flask-SQLAlchemy supports both with minimal config changes. Avoid MySQL unless there's a specific need—Postgres offers better JSON support and fewer bugs.

Conclusion

Flask is the fastest way from idea to live web app if you know Python. Less code than Django, full control over dependencies, and deployable to any Linux server. Have you never shipped a solo web app? Build this task manager, deploy to a $6 VPS, and share the link with a friend. That's your first real product.

Next up: add user authentication with Flask-Login and deploy to Render's free tier. You'll have a multi-user app ready in under two hours. For more insights on email marketing tools for solopreneurs, check out our article on the Best Email Marketing Tools for Solopreneurs in 2026. If you're interested in setting up Mailchimp for your projects, our guide on Step-by-Step Mailchimp Setup for Solo Founders can help you get started.


Editorial note: This article was produced with AI assistance and reviewed by Javier Valencia. Verified facts are distinguished from editorial opinion throughout the text. External sources linked are independent of NewsTide.

Sources

  1. A MacBook with lines of code on its screen on a busy desk
  2. Christopher Gower
  3. official Flask documentation
  4. silver iMac turned on inside room
  5. Lee Campbell

More in Indie Hacking

🇪🇸 Also available in Spanish: Leer en español

𝕏in