How to use Flask in Python
Ready to use Flask in Python? Our guide shows you different methods, tips, real-world examples, and how to debug common errors.

Flask, a Python microframework, lets you build web applications with speed and simplicity. Its core design offers essential tools for web development, so you can start projects without unnecessary complexity.
Here, you'll learn key techniques and practical tips to use Flask effectively. You will explore real-world applications and debugging advice to help you master the framework for your own projects.
Getting started with Flask
from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello_world():
return 'Hello, World!'
if __name__ == '__main__':
app.run(debug=True)--OUTPUT--* Serving Flask app
* Debug mode: on
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
This code creates a simple web server. The line app = Flask(__name__) instantiates your application. Using __name__ is a standard convention that helps Flask locate the application's root path, which is useful for finding templates and static files.
- The
@app.route('/')decorator links the root URL to a specific Python function. - The
hello_world()function is that function—it runs and returns the text to display in the browser.
Running the app with app.run(debug=True) activates a development server with debugging enabled, which automatically reloads on code changes and provides an in-browser debugger for errors.
Core Flask concepts
Building on that simple example, you can create dynamic applications by defining routes with @app.route(), rendering templates, and processing form data with the request object.
Creating different routes with @app.route()
from flask import Flask
app = Flask(__name__)
@app.route('/')
def home():
return 'Home Page'
@app.route('/about')
def about():
return 'About Page'
@app.route('/user/<username>')
def show_user(username):
return f'User: {username}'--OUTPUT--# When accessing http://127.0.0.1:5000/
Home Page
# When accessing http://127.0.0.1:5000/about
About Page
# When accessing http://127.0.0.1:5000/user/john
User: john
You can define multiple routes to build out your application's pages, with each @app.route() decorator mapping a URL path to a unique function. This code demonstrates both static and dynamic routing.
- Static routes like
/abouthave a fixed path that directly corresponds to a function. - Dynamic routes, such as
/user/<username>, use variables in angle brackets. Flask captures the value from the URL and passes it as an argument to the view function,show_user(username), letting you create flexible pages like user profiles.
Using templates with Jinja2
from flask import Flask, render_template
app = Flask(__name__)
@app.route('/hello/<name>')
def hello(name):
return render_template('hello.html', name=name)
# hello.html content:
# <h1>Hello {{ name }}!</h1>--OUTPUT--# When accessing http://127.0.0.1:5000/hello/john
# Browser renders:
<h1>Hello john!</h1>
Flask uses the Jinja2 templating engine to separate your Python logic from your HTML presentation. The render_template() function finds and renders an HTML file, typically from a dedicated templates folder in your project.
- You pass data to the template using keyword arguments. For instance,
name=namesends the captured URL variable to thehello.htmlfile. - Inside the template, expressions like
{{ name }}act as placeholders. Jinja2 replaces them with the actual data you passed, allowing you to generate dynamic HTML pages.
Handling forms with request object
from flask import Flask, request, render_template
app = Flask(__name__)
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
return f'Logged in as {request.form["username"]}'
return render_template('login_form.html')--OUTPUT--# When form is submitted with username "admin"
Logged in as admin
Flask's global request object gives you access to incoming request data, such as form submissions. By adding methods=['GET', 'POST'] to the @app.route() decorator, you enable a single URL to both display a form and process the data it sends.
- When you first visit the page, a
GETrequest renders thelogin_form.htmltemplate. - Submitting the form sends a
POSTrequest, which theif request.method == 'POST'condition catches. - You can then access submitted data via
request.form, a dictionary-like object. For example,request.form['username']retrieves the value from the form's "username" field.
Advanced Flask features
With the core concepts covered, you're ready to tackle more complex challenges like building APIs, organizing large applications, and leveraging Flask's extension ecosystem.
Building a RESTful API with JSON responses
from flask import Flask, jsonify, request
app = Flask(__name__)
@app.route('/api/users', methods=['GET', 'POST'])
def users_api():
if request.method == 'POST':
return jsonify({"status": "User created"}), 201
return jsonify({"users": ["John", "Jane"]})--OUTPUT--# GET request to /api/users
{"users": ["John", "Jane"]}
# POST request to /api/users
{"status": "User created"}
Flask excels at building RESTful APIs that use JSON for data exchange. The key is the jsonify() function, which correctly formats Python dictionaries into JSON responses for clients.
- This single API endpoint handles both
GETandPOSTrequests. - A
GETrequest fetches a list of users. - A
POSTrequest returns a confirmation message and the HTTP status code201, which signals that a new resource was successfully created.
Organizing code with Blueprint
from flask import Flask, Blueprint
auth = Blueprint('auth', __name__, url_prefix='/auth')
@auth.route('/login')
def login():
return 'Login Page'
app = Flask(__name__)
app.register_blueprint(auth)--OUTPUT--# When accessing http://127.0.0.1:5000/auth/login
Login Page
As your application grows, Blueprint objects help you organize it into smaller, reusable components. Think of them as mini-apps for specific features, like user authentication, which keeps your main application file clean and modular.
- A
Blueprintis initialized with aurl_prefix, which adds a common path like/authto all its routes. - You define routes on the blueprint object—for example,
@auth.route('/login')—instead of on the mainapp. - Finally, you connect the blueprint to your application using
app.register_blueprint(), making its routes live.
Working with Flask extensions
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///test.db'
db = SQLAlchemy(app)
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80), unique=True)--OUTPUT--# No visible output, but creates a SQLite database model
# Using db.create_all() would create the database structure
Flask's power comes from its rich ecosystem of extensions, which add features like database support. This example uses Flask-SQLAlchemy to integrate a database. You first configure the database location with app.config['SQLALCHEMY_DATABASE_URI'] and then initialize the extension by binding it to your app with db = SQLAlchemy(app).
- The
Userclass inherits fromdb.Model, which turns a standard Python class into a database table definition. - Inside the class, you use
db.Columnto define the table's columns, likeidandusername, along with their data types and constraints.
Move faster with Replit
Replit is an AI-powered development platform that transforms natural language into working applications. You can describe what you want to build, and Replit Agent creates it—complete with databases, APIs, and deployment.
For the Flask techniques covered in this article, Replit Agent can turn them into production-ready tools:
- Build a user feedback portal with dynamic pages for submissions and a dashboard to view entries, using forms and database models.
- Create a RESTful API that serves product data from a database, complete with endpoints for adding and retrieving items.
- Deploy a multi-section blog application organized with Blueprints, featuring dynamic routes for user profiles and individual posts.
Describe your app idea, and Replit Agent will write the code, test it, and deploy it for you, turning your concept into a live application.
Common errors and challenges
Working with Flask is generally smooth, but you might hit a few common snags that are easy to solve.
Fixing 404 errors with strict_slashes parameter
A frequent source of confusion is the "404 Not Found" error, which can happen because of a trailing slash in a URL. By default, Flask routes are strict about this. For example, a route defined for /profile/ will work, but accessing /profile without the slash will fail.
- To fix this for a specific route, you can set
strict_slashes=Falsein the decorator, like@app.route('/profile', strict_slashes=False). - This change tells Flask to treat both
/profileand/profile/as the same endpoint, making your app's URLs more forgiving.
Debugging template variable rendering with proper context
If a variable in your Jinja2 template appears blank, it's likely a context issue. This means the data wasn't passed correctly from your Python function. When you call render_template(), you must pass your variables as keyword arguments. For example, to use {{ user_name }} in your HTML, your Python code must include render_template('template.html', user_name=some_variable).
Resolving 'Method Not Allowed' errors with proper methods configuration
The "405 Method Not Allowed" error almost always means your route isn't configured to handle the type of request being sent—typically a POST request from a form. By default, Flask routes only listen for GET requests.
- When a user first visits a page with a form, their browser sends a
GETrequest to display it. - When they submit the form, the browser sends a
POSTrequest containing the data. - To allow both, you must explicitly tell the route to accept them by adding
methods=['GET', 'POST']to the@app.route()decorator.
Fixing 404 errors with strict_slashes parameter
A "404 Not Found" error often comes down to a simple trailing slash. By default, Flask treats routes with and without a slash as distinct endpoints. The following code demonstrates this behavior with two routes, /user and /product/, to show how it works.
from flask import Flask
app = Flask(__name__)
@app.route('/user')
def user_page():
return 'User page'
@app.route('/product/')
def product_page():
return 'Product page'
Because the /product/ route is defined with a trailing slash, trying to access /product results in a 404 error. The following example demonstrates how to resolve this by making the route less strict.
from flask import Flask
app = Flask(__name__)
@app.route('/user', strict_slashes=False)
def user_page():
return 'User page'
@app.route('/product', strict_slashes=False)
def product_page():
return 'Product page'
By setting strict_slashes=False in the @app.route() decorator, you make your URLs more forgiving. It tells Flask to treat URLs with and without a trailing slash as the same endpoint, which helps prevent common 404 errors.
- For example, with this parameter, both
/productand/product/will correctly trigger theproduct_page()function. - This is especially useful when users might manually type URLs and forget the trailing slash.
Debugging template variable rendering with proper context
It's a common frustration: your template renders, but the dynamic data is missing. This usually happens when the context—the data passed to the template—isn't structured correctly. The following code demonstrates how a simple naming mismatch can cause this issue.
from flask import Flask, render_template
app = Flask(__name__)
@app.route('/dashboard')
def dashboard():
user_data = {"name": "John", "tasks": ["Task 1", "Task 2"]}
return render_template('dashboard.html', data=user_data)
The function passes the user_data dictionary to the template as data. If your template tries to access user_data.name directly, Jinja2 won't find it, and the variable will appear blank. The template below shows how to correctly access this nested data.
from flask import Flask, render_template
app = Flask(__name__)
@app.route('/dashboard')
def dashboard():
user_data = {"name": "John", "tasks": ["Task 1", "Task 2"]}
return render_template('dashboard.html', name=user_data["name"], tasks=user_data["tasks"])
The corrected code unpacks the dictionary before passing it to render_template(). By sending data as individual keyword arguments, such as name=user_data["name"], you make each piece directly available in the template. This ensures variables like {{ name }} render correctly without extra work.
- This approach simplifies your template logic since you don't need to navigate nested objects.
- Keep an eye on this when passing dictionaries or complex objects to your templates to avoid rendering issues.
Resolving 'Method Not Allowed' errors with proper methods configuration
The "405 Method Not Allowed" error is a classic sign that your route isn't set up for the request it's receiving. This often happens when a form sends a POST request to a route that only accepts GET requests by default.
The following code demonstrates a common scenario where this error occurs because the methods parameter is missing from the route decorator.
from flask import Flask, request, render_template
app = Flask(__name__)
@app.route('/register')
def register():
if request.method == 'POST':
username = request.form['username']
return f"Registered user: {username}"
return render_template('register.html')
Since the @app.route('/register') decorator lacks a methods argument, it only accepts GET requests. This conflicts with the form's POST submission, triggering the error. The corrected code below shows how to resolve this mismatch.
from flask import Flask, request, render_template
app = Flask(__name__)
@app.route('/register', methods=['GET', 'POST'])
def register():
if request.method == 'POST':
username = request.form['username']
return f"Registered user: {username}"
return render_template('register.html')
By adding methods=['GET', 'POST'] to the @app.route() decorator, you explicitly permit the route to handle both request types. This allows the same URL to first display a form with a GET request and then process the submitted data from a POST request, which resolves the error.
- You'll need this configuration for any route that handles form submissions, as it aligns the endpoint's behavior with the browser's actions.
Real-world applications
Now you can combine these concepts to build real-world applications, like a to-do list using redirect() and a weather API with jsonify().
Creating a simple to-do list application with redirect()
This to-do list application uses the redirect() function to reload the page after a new task is submitted, which is a standard way to handle form submissions.
from flask import Flask, request, render_template, redirect, url_for
app = Flask(__name__)
tasks = [] # Simple in-memory storage
@app.route('/', methods=['GET', 'POST'])
def todo_list():
if request.method == 'POST':
task = request.form.get('task')
if task:
tasks.append(task)
return redirect(url_for('todo_list'))
return render_template('todo.html', tasks=tasks)
This application manages a to-do list using a single route that accepts both GET and POST requests. It stores tasks in a simple in-memory list called tasks. When you submit a new task, the app adds it to this list and then uses redirect() to send you back to the main page.
- Using
redirect(url_for('todo_list'))after aPOSTrequest is a common pattern that prevents the browser from resubmitting the form if you refresh the page. - For a standard
GETrequest, the function simply renders thetodo.htmltemplate, passing the currenttaskslist for display.
Building a weather API with requests and jsonify()
You can build a simple weather API by using the requests library to call an external service and jsonify() to format the response.
from flask import Flask, jsonify
import requests
app = Flask(__name__)
@app.route('/api/weather/<city>')
def get_weather(city):
api_key = "YOUR_API_KEY" # Example key
url = f"https://api.openweathermap.org/data/2.5/weather?q={city}&appid={api_key}&units=metric"
data = requests.get(url).json()
return jsonify({"city": city, "temp": data["main"]["temp"]})
This code creates a dynamic API endpoint that fetches real-time weather data. The route /api/weather/<city> captures a city name directly from the URL and passes it to the get_weather function.
- The function constructs a URL and uses the
requestslibrary to call an external weather service. - After receiving the data, it extracts the temperature from the nested JSON response.
- Finally,
jsonify()creates a new, simplified JSON object containing only the city and its temperature, which is sent back to the client.
Get started with Replit
Now, turn these concepts into a real tool. Describe your idea to Replit Agent, like “Build a Flask API that converts currencies” or “Create a project dashboard with a form to add tasks.”
The agent writes the code, tests for errors, and deploys your application. Start building with Replit.
Create and deploy websites, automations, internal tools, data pipelines and more in any programming language without setup, downloads or extra tools. All in a single cloud workspace with AI built in.
Create & deploy websites, automations, internal tools, data pipelines and more in any programming language without setup, downloads or extra tools. All in a single cloud workspace with AI built in.


.png)
.png)