Python Flask Setup: Project Structure, Virtual Environment, and Your First App
A complete Python Flask setup for a new project: create a virtual environment, install Flask and pin requirements.txt, structure the app with create_app and Blueprints, run it with the Flask CLI, load environment variables, and fix the errors beginners hit first.
Updated September 2026 for Flask 3.1 and Python 3.12+. The project layout is the same one I still use, but the run command, the environment variable section, and the common errors list are new.
I came to Python from web development. I had never needed it beyond the odd script until my final-year project needed a Flask backend for an AI model, and the first hour was spent working out what a "proper" Flask project even looks like. This is the setup I landed on and have reused since: a virtual environment, a pinned requirements.txt, an application factory, Blueprints for routes, and a .env file for configuration. It works for a weekend API and it still works when the project grows.
Why Flask
Flask is a small Python web framework. It gives you routing, request handling, and templating, and stays out of the way for everything else. That makes it a good fit for APIs, internal tools, and any project where Django would be more framework than you need. Flask 3 requires Python 3.9 or newer.
Flask project structure
Before typing anything, here is the layout the rest of this guide builds:
flask-app/
├── .env
├── .gitignore
├── main.py
├── requirements.txt
└── src/
├── __init__.py
├── views.py
├── static/
└── templates/
└── base.htmlmain.py: the entry point. It asks the factory for an app and runs it.requirements.txt: the dependency list, so the project installs the same way on every machine..env: local configuration such as the secret key. Never committed.src/__init__.py: makessrca package and holdscreate_app(), the application factory.src/views.py: the routes, grouped in a Blueprint.src/templates/: HTML templates rendered with Jinja.src/static/: CSS, JavaScript, and images.
A single app.py works for a demo, but the moment you add a second file you hit circular imports. The factory layout avoids that from day one.
Step 1: Create a virtual environment
A virtual environment gives the project its own copy of Python packages, so Flask for this project does not collide with Flask for another one, or with anything installed system-wide.
From an empty project folder:
python -m venv .venvOn some systems the command is python3. The folder is named .venv by convention: it is hidden in most file browsers, and tools like VS Code and PyCharm detect it automatically.
Activate it:
# macOS and Linux
source .venv/bin/activate
# Windows (PowerShell)
.venv\Scripts\Activate.ps1
# Windows (Command Prompt)
.venv\Scripts\activate.batThe prompt now starts with (.venv). Every pip install from here on goes into that folder. Add .venv/ to .gitignore before you forget.
If PowerShell refuses to run the activation script, run Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser once, then activate again.
Step 2: Install Flask and pin requirements.txt
With the environment active:
pip install flask python-dotenvpython-dotenv is optional but worth adding now. When it is installed, the Flask CLI reads a .env file automatically, which Step 7 relies on.
Now record the dependencies. There are two ways to write requirements.txt, and beginners usually get told only the first one:
pip freeze > requirements.txtpip freeze writes every installed package with its exact version, including the ones Flask pulled in (Werkzeug, Jinja2, click, and so on). That is a lockfile: perfect for reproducing the environment, noisy to read.
The alternative is to write the file by hand and list only what you asked for:
flask>=3.1,<4
python-dotenv>=1.0I use the hand-written form for small projects and pip freeze when a deployment needs exact versions. Many projects keep both: requirements.txt for what you chose, and a frozen requirements.lock for reproducing the exact environment. Either way, anyone can now recreate the environment with:
pip install -r requirements.txtStep 3: The application factory (create_app)
Create the src folder and inside it __init__.py. This file does two jobs: it turns src into a Python package, and it holds the function that builds the app.
from flask import Flask
def create_app():
app = Flask(__name__)
app.config.from_prefixed_env()
from .views import main
app.register_blueprint(main)
return appWhy a function instead of a module-level app = Flask(__name__)?
- Tests can call
create_app()with different settings and get a fresh app each time. - Extensions (a database, login, CORS) get initialised in one place, in a known order.
- Blueprints are imported inside the function, which sidesteps the circular import you would otherwise get between
__init__.pyandviews.py.
app.config.from_prefixed_env() loads any environment variable starting with FLASK_ into the config, so FLASK_SECRET_KEY=abc becomes app.config["SECRET_KEY"]. Step 7 uses it.
Step 4: Routes with a Blueprint
A Blueprint is a group of routes you register on the app. Small projects have one; larger ones have one per area (auth, api, admin).
from flask import Blueprint, render_template
main = Blueprint("main", __name__)
@main.route("/")
def home():
return render_template("base.html", message="Hello from Flask")
@main.route("/api/health")
def health():
return {"status": "ok"}Returning a dict from a view sends JSON with the right content type. No jsonify call needed for the simple case.
Step 5: A base template
Create src/templates/base.html. Flask looks in a templates folder next to the package by default, so the path matters.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Flask App</title>
</head>
<body>
<h1>{{ message }}</h1>
</body>
</html>{{ message }} is Jinja. Whatever the view passes as message lands there. Later you can turn this into a real base layout with {% block content %}{% endblock %} and have other templates extend it.
Step 6: Run the Flask app
There are two ways to run the app, and it helps to know both because tutorials use them interchangeably.
Option A: the Flask CLI
The Flask CLI needs to know where the app is. Point it at the package and it finds create_app() on its own:
flask --app src run --debug--debug turns on the reloader and the in-browser debugger. Flask auto-discovers app.py or wsgi.py without the flag; for any other name you pass --app. If you add FLASK_APP=src to .env (Step 7), the command shortens to flask run --debug.
Option B: a main.py entry point
Some hosts and IDE run buttons expect a Python file to execute. Create main.py in the project root:
from src import create_app
app = create_app()
if __name__ == "__main__":
app.run(debug=True)Then:
python main.pyEither way you should see:
* Running on http://127.0.0.1:5000
Press CTRL+C to quitOpen that URL for the template, and /api/health for the JSON route.
Debug mode is for your machine only. It reloads on every save and shows an interactive debugger with your source code and variables to anyone who triggers an error. In production, run the app under a WSGI server such as Gunicorn or Waitress with debug off.
Step 7: Environment variables and .env
Anything that differs between your laptop and the server belongs in the environment, not in the code: the secret key, database URLs, API keys. Create .env in the project root:
FLASK_APP=src
FLASK_DEBUG=1
FLASK_SECRET_KEY=change-me-to-something-long-and-randomBecause python-dotenv is installed, flask run loads this file before the app starts. FLASK_APP and FLASK_DEBUG configure the CLI; the FLASK_SECRET_KEY line reaches app.config["SECRET_KEY"] through from_prefixed_env() in the factory.
If you start the app with python main.py instead, load the file yourself at the top of main.py:
from dotenv import load_dotenv
load_dotenv()
from src import create_app
app = create_app()
if __name__ == "__main__":
app.run(debug=True)Add .env to .gitignore. Commit a .env.example with the keys and placeholder values instead, so the next person knows what to fill in.
Common errors when setting up Flask
ModuleNotFoundError: No module named flask
The virtual environment is not active, or you installed Flask into a different one. Activate .venv and run pip install -r requirements.txt again. In VS Code, also pick the .venv interpreter from the command palette.
Could not locate a Flask application
The CLI could not find the app. Pass --app src (or whatever your package is called), or set FLASK_APP in .env. If the package is named differently from the examples, the flag must match.
ImportError: cannot import name from partially initialized module
A circular import. Make sure the Blueprint import lives inside create_app(), not at the top of __init__.py.
Address already in use on port 5000
On macOS, AirPlay Receiver listens on port 5000. Either turn it off in System Settings, or run on another port: flask --app src run --debug --port 5001.
TemplateNotFound: base.html
The templates folder is in the wrong place. It must sit inside the package that creates the app, so src/templates/, not a templates folder at the project root.
jinja2.exceptions.UndefinedError
The template references a variable the view did not pass. Check the keyword arguments in render_template().
What to build next
You now have a Flask project that installs cleanly, runs with one command, and reads configuration from the environment. From here, add a Blueprint per feature, or serve a model behind /api. When you add a database, initialise the extension inside create_app() next to the Blueprints and keep the models in their own module under src; the factory layout is what makes that painless later. If you are pairing it with a Next.js frontend, my Template-NEXT guide covers the other half of that stack, and if the project is not under version control yet, start with initialising a Git repository.
Have a wonderful day.