Tworzenie prostych aplikacji Flask krok po kroku

Kacper Sieradziński
Kacper Sieradziński
18 maja 2025Edukacja4 min czytania

Flask to lekki, elastyczny framework webowy dla Pythona, który daje pełną kontrolę nad strukturą i zachowaniem aplikacji. Nie wymusza sztywnej architektury, nie narzuca ORM ani systemu szablonów — ale dostarcza solidny fundament, na którym możesz zbudować wszystko: od prostego API po złożony system webowy.

Obraz główny Tworzenie prostych aplikacji Flask krok po kroku

Jest to framework, który cenią sobie doświadczeni programiści za minimalizm, przejrzystość i rozszerzalność. W tym przewodniku poznasz podstawy Flask: od instalacji po zaawansowane funkcjonalności, routing, szablony, sesje i integrację z bazą danych.

Instalacja i uruchomienie pierwszej aplikacji

Zacznij od instalacji Flask:

Bash
1 pip install flask

Utwórz plik app.py

Python
1 2 3 4 5 6 7 8 9 10 from flask import Flask app = Flask(__name__) @app.route("/") def hello(): return "Witaj w Flask!" if __name__ == "__main__": app.run(debug=True)

Uruchom aplikację

Bash
1 flask run

Lub bezpośrednio:

Bash
1 python app.py

Flask domyślnie wystartuje na http://127.0.0.1:5000.

Konfiguracja trybu debugowania

Python
1 2 3 4 app = Flask(__name__) if __name__ == "__main__": app.run(debug=True)# Automatyczne przeładowanie przy zmianach

Tryb debugowania włącza:

  • Automatyczne przeładowanie przy zmianach w kodzie
  • Interaktywny debugger w przeglądarce
  • Szczegółowe komunikaty błędów

Routing – obsługa różnych adresów

W Flasku routing to serce aplikacji. Każdy adres URL w Twojej aplikacji jest powiązany z konkretną funkcją (tzw. view function).

Podstawowe routing

Python
1 2 3 4 5 6 7 8 9 10 11 @app.route("/") def home(): return "Strona główna" @app.route("/about") def about(): return "O nas" @app.route("/contact") def contact(): return "Kontakt"

Dynamiczne parametry w URL

Python
1 2 3 4 5 6 7 8 9 10 11 @app.route("/user/<name>") def user(name): return f"Witaj, {name}!" @app.route("/post/<int:id>") def show_post(id): return f"Post ID: {id}" @app.route("/path/<path:subpath>") def show_subpath(subpath): return f"Subpath: {subpath}"

Możesz też określać typy parametrów:

  • <string:name> — domyślny typ (można pominąć)
  • <int:id> — liczba całkowita
  • <float:value> — liczba zmiennoprzecinkowa
  • <path:subpath> — ścieżka (zezwala na slashe)

Obsługa różnych metod HTTP

Python
1 2 3 4 5 6 7 8 9 10 11 12 @app.route("/login", methods=["GET", "POST"]) def login(): if request.method == "POST": # Logika weryfikacji użytkownika i tworzenia sesji return "Zalogowano" # Wyświetl formularz logowania return ''' <form method="POST"> <input type="text" name="username"> <input type="submit" value="Login"> </form> '''

Generowanie URL

Python
1 2 3 4 5 6 7 8 9 from flask import url_for @app.route("/") def index(): return f"<a href='{url_for('user', name='Kacper')}'>Profil</a>" @app.route("/user/<name>") def user(name): return f"Witaj, {name}!"

Szablony HTML z Jinja2

Flask korzysta z silnika szablonów Jinja2, który pozwala tworzyć dynamiczne strony HTML.

Struktura projektu

Bash
1 2 3 4 5 6 7 8 app.py templates/ ├── index.html ├── about.html └── base.html static/ ├── style.css └── script.js

Podstawowe użycie szablonów

app.py:

Python
1 2 3 4 5 6 7 8 9 10 11 from flask import Flask, render_template app = Flask(__name__) @app.route("/") def home(): return render_template("index.html", title="Strona główna", users=["Kacper", "Anna"]) @app.route("/about") def about(): return render_template("about.html", title="O nas")

templates/index.html:

HTML
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <!DOCTYPE html> <html lang="pl"> <head> <title>{{ title }}</title> </head> <body> <h1>Witaj w Flask!</h1> <p>To dynamiczna treść wygenerowana przez Jinja2.</p> <ul> {% for user in users %} <li>{{ user }}</li> {% endfor %} </ul> </body> </html>

Dziedziczenie szablonów

templates/base.html:

HTML
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <!DOCTYPE html> <html lang="pl"> <head> <title>{% block title %}My App{% endblock %}</title> <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}"> </head> <body> <nav> <a href="{{ url_for('home') }}">Strona główna</a> <a href="{{ url_for('about') }}">O nas</a> </nav> <main> {% block content %}{% endblock %} </main> <footer> <p>&copy; 2025 My App</p> </footer> </body> </html>

templates/index.html:

HTML
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 {% extends "base.html" %} {% block title %}{{ title }} - My App{% endblock %} {% block content %} <h1>Witaj w Flask!</h1> <p>To dynamiczna treść.</p> {% if users %} <ul> {% for user in users %} <li>{{ user }}</li> {% endfor %} </ul> {% else %} <p>Brak użytkowników.</p> {% endif %} {% endblock %}

Filtry Jinja2

Jinja2 oferuje wiele przydatnych filtrów:

HTML
1 2 3 4 5 {{ name|upper }} <!-- WIELKIE LITERY --> {{ description|truncate(50) }}<!-- Skróć do 50 znaków --> {{ date|date_format }} <!-- Formatowanie daty --> {{ value|default("Brak") }}<!-- Wartość domyślna --> {{ html|safe }}<!-- Wyłącz escaping HTML -->

Makra i includy

templates/macros.html:

HTML
1 2 3 4 5 6 {% macro render_user(user) %} <div class="user"> <h3>{{ user.name }}</h3> <p>{{ user.email }}</p> </div> {% endmacro %}

templates/index.html:

HTML
1 2 3 4 5 6 7 8 {% extends "base.html" %} {% from "macros.html" import render_user %} {% block content %} {% for user in users %} {{ render_user(user) }} {% endfor %} {% endblock %}

Formularze i dane POST

Obsługa formularzy w Flasku jest bardzo prosta. Możesz pobierać dane z request.form lub request.args.

Podstawowa obsługa formularza

Python
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 from flask import Flask, request, render_template app = Flask(__name__) @app.route("/login", methods=["GET", "POST"]) def login(): if request.method == "POST": username = request.form.get("username") password = request.form.get("password") if username and password: # Weryfikacja danych return f"Witaj, {username}!" else: return "Proszę wypełnić wszystkie pola", 400 return ''' <form method="POST"> <input type="text" name="username" placeholder="Login" required> <input type="password" name="password" placeholder="Hasło" required> <input type="submit" value="Zaloguj"> </form> '''

Obsługa plików

Python
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 from flask import request from werkzeug.utils import secure_filename import os UPLOAD_FOLDER = 'uploads' ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif'} app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER def allowed_file(filename): return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS @app.route("/upload", methods=["GET", "POST"]) def upload_file(): if request.method == "POST": if 'file' not in request.files: return "Brak pliku", 400 file = request.files['file'] if file.filename == '': return "Nie wybrano pliku", 400 if file and allowed_file(file.filename): filename = secure_filename(file.filename) file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename)) return f"Plik {filename} został przesłany!" return ''' <form method="POST" enctype="multipart/form-data"> <input type="file" name="file"> <input type="submit" value="Prześlij"> </form> '''

Flask-WTF dla zaawansowanych formularzy

Dla bardziej złożonych formularzy możesz skorzystać z biblioteki Flask-WTF, która zapewnia walidację danych i integrację z CSRF.

Bash
1 pip install Flask-WTF
Python
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 from flask import Flask, render_template from flask_wtf import FlaskForm from wtforms import StringField, PasswordField, SubmitField from wtforms.validators import DataRequired, Email, Length app = Flask(__name__) app.config['SECRET_KEY'] = 'your-secret-key' class LoginForm(FlaskForm): username = StringField('Username', validators=[DataRequired(), Length(min=3, max=20)]) password = PasswordField('Password', validators=[DataRequired()]) submit = SubmitField('Login') @app.route("/login", methods=["GET", "POST"]) def login(): form = LoginForm() if form.validate_on_submit(): username = form.username.data # Weryfikacja danych użytkownika i utworzenie sesji return f"Witaj, {username}!" return render_template("login.html", form=form)

templates/login.html:

HTML
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 {% extends "base.html" %} {% block content %} <form method="POST"> {{ form.hidden_tag() }} <p> {{ form.username.label }}<br> {{ form.username(size=32) }} {% if form.username.errors %} <span style="color: red;">{{ form.username.errors[0] }}</span> {% endif %} </p> <p> {{ form.password.label }}<br> {{ form.password(size=32) }} </p> <p>{{ form.submit() }}</p> </form> {% endblock %}

Sesje i cookies

Flask ma wbudowany system sesji oparty o ciasteczka podpisane kluczem aplikacji.

Podstawowe użycie sesji

Python
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 from flask import Flask, session, request app = Flask(__name__) app.secret_key = "sekretny-klucz-zmien-w-produkcji" @app.route("/set_user") def set_user(): session["username"] = "Kacper" session["user_id"] = 123 return "Zapisano użytkownika w sesji!" @app.route("/get_user") def get_user(): username = session.get("username", "Anonim") user_id = session.get("user_id") return f"Zalogowany: {username} (ID: {user_id})" @app.route("/logout") def logout(): session.pop("username", None) session.pop("user_id", None) return "Wylogowano"

Sesje są szyfrowane — dane użytkownika nie są przechowywane po stronie serwera, ale w bezpieczny sposób w ciasteczkach. Wymagają SECRET_KEY.

Konfiguracja sesji

Python
1 2 3 4 5 6 7 8 9 10 from datetime import timedelta app.config['SECRET_KEY'] = 'your-secret-key' app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(hours=24) @app.route("/login", methods=["POST"]) def login(): session.permanent = True# Sesja wygasa po PERMANENT_SESSION_LIFETIME session["username"] = request.form["username"] return "Zalogowano"

Bezpośrednia praca z cookies

Python
1 2 3 4 5 6 7 8 9 10 11 12 from flask import make_response @app.route("/set_cookie") def set_cookie(): resp = make_response("Cookie ustawione") resp.set_cookie("preference", "dark_mode", max_age=60*60*24*30)# 30 dni return resp @app.route("/get_cookie") def get_cookie(): preference = request.cookies.get("preference", "light_mode") return f"Preferencja: {preference}"

Integracja z bazą danych

Flask nie ma własnego ORM (jak Django), ale doskonale współpracuje z SQLAlchemy lub Peewee.

Przykład z SQLAlchemy

Instalacja:

Bash
1 pip install flask-sqlalchemy

app.py:

Python
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 from flask import Flask, render_template from flask_sqlalchemy import SQLAlchemy from datetime import datetime app = Flask(__name__) app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///users.db" app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False db = SQLAlchemy(app) class User(db.Model): id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(50), nullable=False) email = db.Column(db.String(100), unique=True, nullable=False) created_at = db.Column(db.DateTime, default=datetime.utcnow) def __repr__(self): return f"<User {self.name}>" # Inicjalizacja bazy danych with app.app_context(): db.create_all() @app.route("/add/<name>/<email>") def add_user(name, email): user = User(name=name, email=email) db.session.add(user) db.session.commit() return f"Dodano użytkownika {name}" @app.route("/users") def list_users(): users = User.query.all() return render_template("users.html", users=users) @app.route("/user/<int:user_id>") def get_user(user_id): user = User.query.get_or_404(user_id) return render_template("user.html", user=user)

Ten przykład tworzy prostą bazę danych SQLite i dodaje użytkowników poprzez endpoint.

Migracje z Flask-Migrate

Bash
1 pip install Flask-Migrate
Python
1 2 3 from flask_migrate import Migrate migrate = Migrate(app, db)
Bash
1 2 3 flask db init flask db migrate -m "Initial migration" flask db upgrade

Refaktoryzacja – Blueprints

Kiedy aplikacja rośnie, warto ją modularnie rozdzielić. W Flasku służą do tego Blueprints — odpowiednik routerów w FastAPI lub aplikacji w Django.

Struktura projektu

Bash
1 2 3 4 5 6 7 8 app/ ├── __init__.py ├── main/ │ ├── __init__.py │ └── routes.py └── users/ ├── __init__.py └── routes.py

app/main/routes.py:

Python
1 2 3 4 5 6 7 8 9 10 11 from flask import Blueprint, render_template main = Blueprint("main", __name__) @main.route("/") def home(): return render_template("index.html", title="Strona główna") @main.route("/about") def about(): return render_template("about.html", title="O nas")

app/users/routes.py:

Python
1 2 3 4 5 6 7 8 9 10 11 12 13 14 from flask import Blueprint, render_template from app.models import User users = Blueprint("users", __name__, url_prefix="/users") @users.route("/") def list_users(): users_list = User.query.all() return render_template("users/list.html", users=users_list) @users.route("/<int:user_id>") def show_user(user_id): user = User.query.get_or_404(user_id) return render_template("users/detail.html", user=user)

app/init.py:

Python
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 from flask import Flask from app.main.routes import main from app.users.routes import users def create_app(): app = Flask(__name__) app.config['SECRET_KEY'] = 'your-secret-key' app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///users.db' from app.models import db db.init_app(app) app.register_blueprint(main) app.register_blueprint(users) return app

run.py:

Python
1 2 3 4 5 6 from app import create_app app = create_app() if __name__ == "__main__": app.run(debug=True)

To podejście pozwala skalować aplikację bez utraty przejrzystości. Każdy blueprint może mieć własne szablony, statyczne pliki i konfigurację.

Konfiguracja i środowiska

Flask wspiera różne środowiska (dev, test, production). Wystarczy przygotować plik config.py:

config.py:

Python
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 import os class Config: SECRET_KEY = os.environ.get('SECRET_KEY') or 'dev-secret-key' SQLALCHEMY_TRACK_MODIFICATIONS = False class DevelopmentConfig(Config): DEBUG = True SQLALCHEMY_DATABASE_URI = os.environ.get('DEV_DATABASE_URL') or 'sqlite:///dev.db' class TestingConfig(Config): TESTING = True SQLALCHEMY_DATABASE_URI = 'sqlite:///:memory:' WTF_CSRF_ENABLED = False class ProductionConfig(Config): DEBUG = False SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL') or 'postgresql://user:pass@localhost/prod'

app/init.py:

Python
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 from flask import Flask from config import Config, DevelopmentConfig, TestingConfig, ProductionConfig config = { 'development': DevelopmentConfig, 'testing': TestingConfig, 'production': ProductionConfig } def create_app(config_name='development'): app = Flask(__name__) app.config.from_object(config[config_name]) # Inicjalizacja rozszerzeń from app.models import db db.init_app(app) # Rejestracja blueprintów from app.main.routes import main from app.users.routes import users app.register_blueprint(main) app.register_blueprint(users) return app

run.py:

Python
1 2 3 4 5 6 7 8 import os from app import create_app config_name = os.getenv('FLASK_ENV', 'development') app = create_app(config_name) if __name__ == "__main__": app.run()

Zmienne środowiskowe

Używaj .env do przechowywania konfiguracji:

Bash
1 pip install python-decouple

.env:

INI
1 2 3 SECRET_KEY=super-secret-key DATABASE_URL=postgresql://user:pass@localhost/db FLASK_ENV=development

config.py:

Python
1 2 3 4 5 from decouple import config class Config: SECRET_KEY = config('SECRET_KEY') SQLALCHEMY_DATABASE_URI = config('DATABASE_URL')

Testowanie aplikacji Flask

Testy w Flasku są proste i szybkie dzięki pytest lub unittest.

Podstawowe testy z unittest

Python
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 import unittest from app import create_app class FlaskTestCase(unittest.TestCase): def setUp(self): self.app = create_app('testing') self.client = self.app.test_client() self.app_context = self.app.app_context() self.app_context.push() def tearDown(self): self.app_context.pop() def test_home(self): response = self.client.get("/") self.assertEqual(response.status_code, 200) self.assertIn(b"Witaj", response.data) def test_login(self): response = self.client.post("/login", data={ "username": "test", "password": "test123" }) self.assertEqual(response.status_code, 200)

Testy z pytest

Python
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 import pytest from app import create_app @pytest.fixture def client(): app = create_app('testing') with app.test_client() as client: with app.app_context(): yield client def test_home(client): response = client.get("/") assert response.status_code == 200 assert b"Witaj" in response.data def test_login(client): response = client.post("/login", data={ "username": "test", "password": "test123" }) assert response.status_code == 200

Uruchom testy:

Bash
1 pytest -v

Wdrożenie aplikacji Flask

Gotową aplikację Flask możesz wdrożyć na różne sposoby:

Render, Railway, Fly.io

Szybkie platformy cloudowe, które automatycznie wykrywają Flask:

requirements.txt:

Bash
1 2 Flask==3.0.0 gunicorn==21.2.0

Procfile (dla Railway/Render):

Bash
1 web: gunicorn app:app

Docker

Przykład Dockerfile:

Dockerfile
1 2 3 4 5 6 7 8 9 10 FROM python:3.11-slim WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY . . CMD ["gunicorn", "-b", "0.0.0.0:8000", "--workers", "4", "app:app"]

Gunicorn + Nginx

Uruchomienie z Gunicorn:

Bash
1 2 pip install gunicorn gunicorn -w 4 -b 0.0.0.0:8000 app:app

Konfiguracja Nginx:

NGINX
1 2 3 4 5 6 7 8 9 10 server { listen 80; server_name example.com; location / { proxy_pass http://127.0.0.1:8000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }

Najlepsze praktyki

Poniżej znajdziesz zestaw najlepszych praktyk do tworzenia aplikacji Flask:

1. Oddziel logikę biznesową od widoków

Python
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 # services/user_service.py class UserService: @staticmethod def create_user(name, email): # Logika biznesowa user = User(name=name, email=email) db.session.add(user) db.session.commit() return user # routes/users.py from app.services.user_service import UserService @users.route("/", methods=["POST"]) def create_user(): data = request.json user = UserService.create_user(data['name'], data['email']) return jsonify(user.to_dict()), 201

2. Używaj Blueprints

Modularna architektura to klucz do skalowalności. Każdy blueprint powinien odpowiadać za jedną funkcjonalność.

3. Zabezpieczaj formularze (CSRF, XSS)

Używaj Flask-WTF do ochrony przed CSRF:

Python
1 2 3 from flask_wtf.csrf import CSRFProtect csrf = CSRFProtect(app)

W szablonach:

HTML
1 2 3 4 <form method="POST"> {{ csrf_token() }} <!-- pola formularza --> </form>

4. Korzystaj z .env do przechowywania sekretów

Nigdy nie commituj kluczy i haseł do repozytorium. Używaj zmiennych środowiskowych.

5. Stosuj testy jednostkowe i integracyjne

Pisz testy dla każdego endpointu i logiki biznesowej.

6. Zadbaj o logowanie i obsługę błędów

Python
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 import logging from logging.handlers import RotatingFileHandler if not app.debug: file_handler = RotatingFileHandler('logs/app.log', maxBytes=10240, backupCount=10) file_handler.setFormatter(logging.Formatter( '%(asctime)s %(levelname)s: %(message)s [in %(pathname)s:%(lineno)d]' )) file_handler.setLevel(logging.INFO) app.logger.addHandler(file_handler) app.logger.setLevel(logging.INFO) @app.errorhandler(404) def not_found_error(error): return render_template('errors/404.html'), 404 @app.errorhandler(500) def internal_error(error): db.session.rollback() return render_template('errors/500.html'), 500

7. Używaj migracji bazy danych

Flask-Migrate pozwala zarządzać zmianami w schemacie bazy danych.

8. Cache'uj odpowiedzi dla lepszej wydajności

Bash
1 pip install Flask-Caching
Python
1 2 3 4 5 6 7 8 9 from flask_caching import Cache cache = Cache(app, config={'CACHE_TYPE': 'simple'}) @app.route("/expensive") @cache.cached(timeout=300) def expensive_operation(): # Kosztowna operacja return expensive_result

Podsumowanie

Flask to framework, który daje Ci pełną kontrolę i prostotę. Jest lekki, intuicyjny i pozwala na szybkie prototypowanie.

Świetnie sprawdza się w:

  • małych i średnich aplikacjach,
  • API backendowych,
  • serwisach do automatyzacji lub dashboardach,
  • prototypach i MVP (Minimum Viable Product).

Najważniejsze cechy Flask:

  • Minimalizm — tylko to, czego potrzebujesz
  • Elastyczność — pełna kontrola nad strukturą
  • Rozszerzalność — bogaty ekosystem rozszerzeń
  • Prostota — łatwa nauka i czytelny kod
  • Wsparcie społeczności — duża społeczność i dokumentacja

Jeśli szukasz frameworka, który "nie przeszkadza" i pozwala budować aplikacje po swojemu, Flask to idealny wybór. Zacznij od prostych endpointów, stopniowo dodawaj funkcjonalności i rozbudowuj aplikację zgodnie z potrzebami projektu.