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.

How to use Flask in Python
Published on: 
Tue
Mar 17, 2026
Updated on: 
Fri
Mar 20, 2026
The Replit Team

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 /about have 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=name sends the captured URL variable to the hello.html file.
  • 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 GET request renders the login_form.html template.
  • Submitting the form sends a POST request, which the if 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 GET and POST requests.
  • A GET request fetches a list of users.
  • A POST request returns a confirmation message and the HTTP status code 201, 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 Blueprint is initialized with a url_prefix, which adds a common path like /auth to all its routes.
  • You define routes on the blueprint object—for example, @auth.route('/login')—instead of on the main app.
  • 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 User class inherits from db.Model, which turns a standard Python class into a database table definition.
  • Inside the class, you use db.Column to define the table's columns, like id and username, 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=False in the decorator, like @app.route('/profile', strict_slashes=False).
  • This change tells Flask to treat both /profile and /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 GET request to display it.
  • When they submit the form, the browser sends a POST request 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 /product and /product/ will correctly trigger the product_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 a POST request is a common pattern that prevents the browser from resubmitting the form if you refresh the page.
  • For a standard GET request, the function simply renders the todo.html template, passing the current tasks list 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 requests library 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.

Get started free

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.

Get started for free

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.