Build a Simple Website Using Flask: The Complete Beginner’s Guide (2026)

You have learned Python basics. You can write functions, handle loops, and maybe even work with files. But now you are asking the real question: “How do I turn my Python knowledge into an actual website that people can visit?”

That is exactly where Flask comes in — and it is one of the most beginner-friendly answers to that question in the Python ecosystem.

Flask is a lightweight Python web framework that lets you go from a blank Python file to a running website in under ten minutes. No complicated setup. No overwhelming configuration. Just clean, readable Python code that makes the web work for you.

By the time you finish this guide, you will have:

  • A solid understanding of what Flask is and how it works
  • A fully configured Flask development environment
  • The skills to build dynamic web pages using Jinja2 templates
  • A working contact form that handles real user input
  • A complete mini project: a personal website with three pages and a contact form

Whether you want to build a portfolio, a tool for your team, or just understand how web applications work, this is the right place to start. Let’s build something real.


What Is Flask? (And Why Not Django?)

Before writing a single line of code, it helps to understand what Flask actually is — and why so many developers, from total beginners to professional engineers, reach for it first.

Flask: The Lean, Mean Web Machine

Flask is a micro web framework for Python. The word “micro” here does not mean it is weak or limited. It means Flask is lean — it gives you exactly the tools you need to build a web application without forcing a dozen extra things on you.

At its core, Flask is built on two powerful libraries:

  • Werkzeug — handles HTTP requests and responses (the plumbing of the web)
  • Jinja2 — renders dynamic HTML templates using Python-like syntax

That is really all Flask needs to do its job. When you want more — a database, user authentication, email sending — you simply add an extension. Flask never forces a direction on you.

Here is a real-world perspective: companies like Pinterest, Reddit, and LinkedIn have used Flask to power parts of their infrastructure. It is not just a teaching toy — it is production-grade software used at scale.

Flask vs Django — Which Should a Beginner Learn?

This is one of the most common questions new Python web developers ask. Here is an honest, practical comparison:

FeatureFlaskDjango
TypeMicro-frameworkFull-stack framework
Learning CurveLow — beginner-friendlySteeper — more concepts upfront
Project StructureYou decideFollows strict conventions
Built-in FeaturesMinimal (add what you need)Built-in ORM, admin, auth
Best ForLearning, APIs, small-to-mid appsLarge platforms, content sites
FlexibilityVery highMore opinionated

For beginners, Flask wins. It lets you learn web development concepts one at a time without overwhelming you with Django’s “batteries included” complexity. Once you understand how Flask works, picking up Django later becomes much easier — because you already understand the underlying concepts.


Installing Flask and Setting Up Your Environment

A good development environment is the foundation of every successful project. This section walks you through a clean, modern setup that follows current best practices as of 2026.

Step 1 — Check Your Python Version

Flask requires Python 3.7 or higher. Flask officially dropped support for Python 2 starting with version 2.0, so any modern Python 3 installation will work. For best performance and compatibility with current extensions, Python 3.11 or later is strongly recommended.

Open your terminal or command prompt and run:

python --version
# or on some systems:
python3 --version

If you see Python 3.11.x or higher, you are good to go. If not, download the latest stable version from the official Python website.

Step 2 — Create a Project Folder

Keep things organized from the start. Create a dedicated folder for your Flask project:

mkdir my_flask_site
cd my_flask_site

Step 3 — Set Up a Virtual Environment

This step is one that beginners often skip — and later regret. A virtual environment creates an isolated Python workspace specifically for your project. This prevents package conflicts between different projects on your machine.

# Create the virtual environment
python -m venv venv

# Activate it — Mac/Linux:
source venv/bin/activate

# Activate it — Windows:
venv\Scripts\activate

When your virtual environment is active, you will see (venv) at the beginning of your terminal prompt. That tells you all packages you install will be scoped to this project only.

💡 Pro Tip: Never skip the virtual environment step. It is a habit that separates hobbyists from professional developers.

Step 4 — Install Flask

With your virtual environment active, install Flask using pip:

pip install flask

This single command installs Flask along with its two core dependencies: Werkzeug (HTTP handling) and Jinja2 (templating). Verify the installation:

flask --version

You should see output similar to:

Python 3.11.x
Flask 3.1.x
Werkzeug 3.x.x

Step 5 — Save Your Dependencies

Always record your project’s dependencies. This allows anyone (including future you) to recreate the environment easily:

pip freeze > requirements.txt

To reinstall everything later on a new machine:

pip install -r requirements.txt

Your project folder now looks like this:

my_flask_site/
├── venv/
└── requirements.txt

You are ready to write your first Flask application.


Creating Your First Flask App

Let us write the simplest possible Flask application and get it running in a browser. This will prove your environment is configured correctly and introduce the fundamental building blocks of every Flask app.

The Minimal Flask Application

Create a new file called app.py in your project folder:

from flask import Flask

# Create the Flask application instance
app = Flask(__name__)

# Define your first route
@app.route('/')
def home():
    return "Hello, World! Welcome to my Flask website."

# Run the app when this file is executed directly
if __name__ == '__main__':
    app.run(debug=True)

That is a complete, working web application in just 10 lines of Python. Let us break down what each part does:

LineExplanation
from flask import FlaskImports the Flask class from the flask package
app = Flask(__name__)Creates your Flask app instance; __name__ tells Flask where to find resources
@app.route('/')A decorator that maps the URL / to the function below it
def home():The view function that runs when someone visits /
return "Hello, World!"The HTTP response sent back to the browser
app.run(debug=True)Starts the development server with auto-reload enabled

Running Your Flask App

In your terminal (with the virtual environment active), run:

flask run

You will see output like this:

 * Running on http://127.0.0.1:5000
 * Debug mode: on

Open your browser and visit http://127.0.0.1:5000. You should see:

Hello, World! Welcome to my Flask website.

Congratulations — you just built and ran your first Flask web application.

⚠️ Important: debug=True is only for development. It enables detailed error messages and automatic server restarts when you save changes. Never use debug=True in a production environment — it exposes sensitive server information to the public.


Understanding Routing in Flask

Routing is the system that connects URLs to Python functions. When a user visits yoursite.com/about, Flask needs to know which Python function to call. That mapping is what routing handles.

Basic Routes

You can define as many routes as your site needs:

from flask import Flask

app = Flask(__name__)

@app.route('/')
def home():
    return "Welcome to the Home Page"

@app.route('/about')
def about():
    return "This is the About Page"

@app.route('/contact')
def contact():
    return "This is the Contact Page"

Each @app.route() decorator connects a URL path to a view function. The function name does not have to match the URL, but it is good practice to keep them descriptive and consistent.

Dynamic URL Parameters

Flask lets you capture parts of the URL as variables. This is how you build dynamic pages — like user profiles or blog post pages:

@app.route('/user/<username>')
def profile(username):
    return f"Hello, {username}! This is your profile page."

@app.route('/post/<int:post_id>')
def show_post(post_id):
    return f"Showing post number: {post_id}"

The <username> syntax captures any string from the URL. The <int:post_id> syntax captures an integer. Visit http://127.0.0.1:5000/user/Sarah and Flask will pass "Sarah" as the username argument to your function.

HTTP Methods — GET and POST

Every web request has an HTTP method. The two most common are:

  • GET — Used to retrieve data (loading a page, clicking a link)
  • POST — Used to submit data (submitting a form)

By default, Flask routes only accept GET requests. To accept POST requests (for forms), specify the methods parameter:

from flask import Flask, request

app = Flask(__name__)

@app.route('/submit', methods=['GET', 'POST'])
def submit():
    if request.method == 'POST':
        name = request.form['name']
        return f"Thank you, {name}! We received your submission."
    return "Please fill out the form."

Using url_for() for Safe URL Generation

Hardcoding URL strings in templates is fragile. If you ever change a route, every link to it breaks. The url_for() function solves this by generating URLs from function names instead of strings:

from flask import url_for

# Instead of: href="/about"
# Use: href="{{ url_for('about') }}"

This way, if you change your /about route to /about-us, your links still work automatically. Always use url_for() in your templates — it is a Flask best practice that pays dividends as your project grows.


Templates with Jinja2

Returning raw strings from view functions works for testing, but real websites need HTML. Flask uses the Jinja2 template engine to render proper HTML pages with dynamic content.

What Is a Template?

A template is an HTML file with special placeholder syntax that Jinja2 processes before sending the page to the browser. Think of it like a form letter — the structure stays the same, but specific values get filled in dynamically based on data from your Python code.

Setting Up the Templates Folder

Flask automatically looks for templates in a folder named templates/. Create it now:

my_flask_site/
├── app.py
├── requirements.txt
├── templates/
│   └── index.html
└── venv/

Your First Template

Create 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>{{ page_title }}</title>
</head>
<body>
    <h1>Welcome, {{ username }}!</h1>
    <p>This is your Flask-powered website.</p>
</body>
</html>

Notice the {{ page_title }} and {{ username }} — these are Jinja2 variables that get replaced with real values when the template is rendered.

Rendering Templates in Flask

Update app.py to render this template:

from flask import Flask, render_template

app = Flask(__name__)

@app.route('/')
def home():
    return render_template('index.html', page_title='My Flask Site', username='Sarah')

The render_template() function loads the HTML file, passes your Python variables into it via keyword arguments, and returns the final rendered HTML to the browser.

Jinja2 Syntax Quick Reference

Jinja2 uses three types of delimiters:

DelimiterPurposeExample
{{ }}Output a variable{{ username }}
{% %}Control logic (if, for, block){% if user %}
{# #}Comments (not shown in HTML){# This is a comment #}

Variables:

<h1>Hello, {{ name }}!</h1>
<p>You have {{ message_count }} new messages.</p>

Conditional Statements:

{% if user_logged_in %}
    <p>Welcome back, {{ username }}!</p>
{% else %}
    <p>Please <a href="/login">log in</a> to continue.</p>
{% endif %}

For Loops:

<ul>
{% for item in shopping_list %}
    <li>{{ item }}</li>
{% endfor %}
</ul>

Template Inheritance — The Base Layout Pattern

One of Jinja2’s most powerful features is template inheritance. Instead of copying the same <head>, navigation, and footer HTML into every page, you create one base layout and extend it across all your pages.

Create templates/base.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{% block title %}My Flask Site{% endblock %}</title>
    <link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
</head>
<body>

    <!-- Navigation Bar -->
    <nav>
        <a href="{{ url_for('home') }}">Home</a>
        <a href="{{ url_for('about') }}">About</a>
        <a href="{{ url_for('contact') }}">Contact</a>
    </nav>

    <!-- Main Content Block -->
    <main>
        {% block content %}
        {% endblock %}
    </main>

    <!-- Footer -->
    <footer>
        <p>&copy; 2026 My Flask Website. All rights reserved.</p>
    </footer>

</body>
</html>

Now create templates/index.html that extends this base:

{% extends "base.html" %}

{% block title %}Home — My Flask Site{% endblock %}

{% block content %}
    <h1>Welcome to My Website</h1>
    <p>This site is built with Python and Flask.</p>
{% endblock %}

With template inheritance, your navigation and footer live in one place. If you need to update them, you change one file — not twenty.

Security Note — Jinja2 Auto-Escaping

Jinja2 automatically escapes HTML characters in variables to prevent XSS (Cross-Site Scripting) attacks. This means if a user submits <script>alert('hacked')</script> into a form, Jinja2 will display it as text — not execute it as code.

<!-- Safe — Jinja2 escapes this automatically -->
<p>{{ user_comment }}</p>

<!-- DANGER — Only use | safe on content YOU fully control -->
<p>{{ trusted_html_content | safe }}</p>

Never use | safe on user-submitted content. This is a critical security rule.


Handling Forms and User Input

Forms are how websites collect information from users. In Flask, handling forms involves two steps: displaying the form (GET request) and processing its data (POST request).

Before diving into web forms, make sure you are comfortable with how Python itself handles input at the language level. If you need a refresher, this guide on how to take user input in Python covers the fundamentals that underpin everything Flask does with user data.

Step 1 — Create the Form Template

Create templates/contact.html:

{% extends "base.html" %}

{% block title %}Contact — My Flask Site{% endblock %}

{% block content %}
<h1>Get In Touch</h1>

{% if success %}
    <div class="success-message">
        <p>✅ Thank you, {{ submitted_name }}! Your message has been received.</p>
    </div>
{% endif %}

<form action="/contact" method="POST">
    <div class="form-group">
        <label for="name">Your Name:</label>
        <input type="text" id="name" name="name" required placeholder="Jane Doe">
    </div>

    <div class="form-group">
        <label for="email">Your Email:</label>
        <input type="email" id="email" name="email" required placeholder="jane@example.com">
    </div>

    <div class="form-group">
        <label for="message">Your Message:</label>
        <textarea id="message" name="message" rows="5" required placeholder="Write your message here..."></textarea>
    </div>

    <button type="submit">Send Message</button>
</form>
{% endblock %}

Step 2 — Process the Form in Flask

Update app.py to handle the contact form:

from flask import Flask, render_template, request

app = Flask(__name__)

@app.route('/contact', methods=['GET', 'POST'])
def contact():
    success = False
    submitted_name = ''

    if request.method == 'POST':
        name = request.form.get('name', '').strip()
        email = request.form.get('email', '').strip()
        message = request.form.get('message', '').strip()

        # Basic validation
        if name and email and message:
            success = True
            submitted_name = name
            # Here you could save to a file, database, or send an email

    return render_template('contact.html', success=success, submitted_name=submitted_name)

Understanding request.form

When a user submits a form, Flask stores all form data in request.form — a dictionary-like object. Use .get('field_name', '') instead of direct access ['field_name'] to safely handle cases where a field might be missing.

Using Flask-WTF for Production Forms

For any serious project, the Flask-WTF extension adds essential features: CSRF protection, built-in validation, and cleaner code:

pip install flask-wtf
from flask_wtf import FlaskForm
from wtforms import StringField, TextAreaField, SubmitField
from wtforms.validators import DataRequired, Email

class ContactForm(FlaskForm):
    name = StringField('Name', validators=[DataRequired()])
    email = StringField('Email', validators=[DataRequired(), Email()])
    message = TextAreaField('Message', validators=[DataRequired()])
    submit = SubmitField('Send Message')

Flask-WTF automatically adds a CSRF token to every form, protecting your users from cross-site request forgery attacks. Always set a strong SECRET_KEY when using Flask-WTF:

app.config['SECRET_KEY'] = 'your-strong-random-secret-key-here'

Working with Static Files

A website without styling looks unfinished. Flask serves CSS, JavaScript, and image files from a dedicated folder called static/.

Setting Up the Static Folder

Create this structure inside your project:

my_flask_site/
├── app.py
├── requirements.txt
├── static/
│   ├── css/
│   │   └── style.css
│   ├── js/
│   │   └── script.js
│   └── images/
│       └── logo.png
├── templates/
│   ├── base.html
│   ├── index.html
│   ├── about.html
│   └── contact.html
└── venv/

Linking Static Files in Templates

Always use url_for('static', filename='...') to reference static files. Never hardcode paths like /static/css/style.css — if your app is deployed at a subpath, hardcoded paths will break:

<!-- In base.html <head> section -->

<!-- CSS Stylesheet -->
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">

<!-- Favicon -->
<link rel="icon" href="{{ url_for('static', filename='images/favicon.ico') }}">

<!-- JavaScript file (usually before </body>) -->
<script src="{{ url_for('static', filename='js/script.js') }}"></script>

Adding Basic CSS Styling

Create static/css/style.css with clean, readable styles:

/* Reset & Base Styles */
* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

body {
    font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
    background-color: #f8f9fa;
    color: #333;
    line-height: 1.6;
}

/* Navigation */
nav {
    background-color: #2c3e50;
    padding: 1rem 2rem;
    display: flex;
    gap: 1.5rem;
}

nav a {
    color: #fff;
    text-decoration: none;
    font-weight: 500;
    transition: color 0.2s;
}

nav a:hover {
    color: #3498db;
}

/* Main Content */
main {
    max-width: 800px;
    margin: 2rem auto;
    padding: 0 1rem;
}

h1 {
    color: #2c3e50;
    margin-bottom: 1rem;
}

/* Form Styles */
.form-group {
    margin-bottom: 1.2rem;
}

label {
    display: block;
    font-weight: 600;
    margin-bottom: 0.4rem;
}

input, textarea {
    width: 100%;
    padding: 0.6rem;
    border: 1px solid #ccc;
    border-radius: 4px;
    font-size: 1rem;
}

button {
    background-color: #3498db;
    color: white;
    padding: 0.7rem 1.5rem;
    border: none;
    border-radius: 4px;
    font-size: 1rem;
    cursor: pointer;
}

button:hover {
    background-color: #2980b9;
}

/* Success Message */
.success-message {
    background-color: #d4edda;
    color: #155724;
    padding: 1rem;
    border-radius: 4px;
    margin-bottom: 1.5rem;
}

/* Footer */
footer {
    text-align: center;
    padding: 2rem;
    background-color: #2c3e50;
    color: #aaa;
    margin-top: 3rem;
}

Serving Images

Place image files in static/images/ and reference them in templates:

<img src="{{ url_for('static', filename='images/profile-photo.jpg') }}" 
     alt="Profile photo of Jane Doe" 
     width="200">

Flask Project Structure Best Practices for Beginners

Flask gives you complete freedom in how you organize your project. That freedom is a strength — but it can also lead to messy, hard-to-maintain code if you have no guiding structure.

The Beginner Structure (Single File)

For small projects with fewer than 200–300 lines of code, a single-file approach works perfectly:

my_flask_site/
├── app.py            ← All routes, configuration, and logic
├── requirements.txt  ← Package dependencies
├── static/
│   ├── css/
│   │   └── style.css
│   └── images/
└── templates/
    ├── base.html
    ├── index.html
    ├── about.html
    └── contact.html

This structure is clean, easy to understand, and completely manageable for beginner projects.

The Intermediate Structure (Package Layout)

When your project grows beyond a single file, it is time to split things up into a Python package. This is not necessary for your first project, but good to know for your future:

my_flask_site/
├── app/
│   ├── __init__.py     ← Creates the Flask app instance
│   ├── routes.py       ← All your @app.route() definitions
│   ├── forms.py        ← Flask-WTF form classes
│   ├── templates/
│   └── static/
├── config.py           ← App configuration (SECRET_KEY, DEBUG, etc.)
├── run.py              ← Entry point to start the app
└── requirements.txt

Key Rules for a Clean Structure

1. Keep your templates/ folder inside your app directory. Flask looks there by default.

2. Keep your static/ folder organized. Subdirectories like css/, js/, and images/ prevent clutter.

3. Use a requirements.txt file. This is not optional — it is professional practice.

4. Never commit your venv/ folder to version control. Add it to .gitignore:

venv/
__pycache__/
*.pyc
.env

5. Store sensitive data in environment variables, not in your code. Your SECRET_KEY and any API keys should never be hardcoded in app.py.


Running and Testing Your Flask App

Running in Development Mode

The recommended way to run Flask in development (using Flask 3.x):

# Basic run
flask run

# With debug mode (auto-reload + detailed errors)
flask --debug run

# On a specific port
flask run --port=8080

When debug mode is on, Flask watches your files for changes. Every time you save app.py or any template, the server restarts automatically — no need to stop and restart manually.

Testing Your Routes

Open your browser and visit each route:

  • http://127.0.0.1:5000/ — Home page
  • http://127.0.0.1:5000/about — About page
  • http://127.0.0.1:5000/contact — Contact form

Reading Flask’s terminal output is an important skill. When you make a request, Flask logs it:

127.0.0.1 - - [25/Apr/2026 10:30:45] "GET / HTTP/1.1" 200 -
127.0.0.1 - - [25/Apr/2026 10:30:52] "POST /contact HTTP/1.1" 200 -

The 200 means success. 404 means route not found. 500 means a server error occurred.

Basic Testing with pytest

Flask has a built-in test client that lets you simulate HTTP requests without a browser. This is essential for catching bugs before they reach users.

Install pytest:

pip install pytest

Create a test_app.py file:

import pytest
from app import app

@pytest.fixture
def client():
    app.config['TESTING'] = True
    with app.test_client() as client:
        yield client

def test_home_page(client):
    response = client.get('/')
    assert response.status_code == 200

def test_about_page(client):
    response = client.get('/about')
    assert response.status_code == 200

def test_contact_form_get(client):
    response = client.get('/contact')
    assert response.status_code == 200

def test_contact_form_post(client):
    response = client.post('/contact', data={
        'name': 'Test User',
        'email': 'test@example.com',
        'message': 'This is a test message'
    })
    assert response.status_code == 200

Run your tests:

pytest test_app.py -v

Writing even a handful of tests saves you hours of manual clicking and dramatically increases your confidence when making changes.


Common Mistakes Beginners Make in Flask (And How to Avoid Them)

Every beginner makes these mistakes. Knowing them in advance saves you hours of frustration.

Mistake 1 — Skipping the Virtual Environment

The problem: Installing Flask globally pollutes your system Python and causes version conflicts between projects.

The fix: Always create and activate a virtual environment before installing anything:

python -m venv venv
source venv/bin/activate  # Mac/Linux

Mistake 2 — Leaving debug=True in Production

The problem: Debug mode exposes your server’s internal state, file paths, and code to anyone who triggers an error. This is a serious security vulnerability.

The fix: Use debug mode only during development. In production, set:

app.run(debug=False)
# Or better: use environment variables

Mistake 3 — Hardcoding URLs Instead of Using url_for()

The problem: Writing href="/about" in templates. If you ever rename the route, every link breaks.

The fix:

<!-- Wrong -->
<a href="/about">About</a>

<!-- Correct -->
<a href="{{ url_for('about') }}">About</a>

Mistake 4 — No CSRF Protection on Forms

The problem: Without CSRF tokens, your forms are vulnerable to cross-site request forgery attacks where malicious websites can submit forms on behalf of your users.

The fix: Use Flask-WTF for all forms. It automatically handles CSRF protection. Set a proper SECRET_KEY in your app configuration.

Mistake 5 — Putting Everything in One Giant app.py

The problem: A 1,000-line app.py file with routes, forms, configuration, and business logic all mixed together becomes impossible to navigate and debug.

The fix: Organize your code from the start. Keep routes in routes.py, forms in forms.py, and configuration in config.py once the project grows past 200 lines.

Mistake 6 — Accessing Form Data Without Checking the Method

The problem:

# WRONG — crashes on GET requests
name = request.form['name']

The fix:

# CORRECT — safely handle both GET and POST
@app.route('/contact', methods=['GET', 'POST'])
def contact():
    if request.method == 'POST':
        name = request.form.get('name', '')

Mistake 7 — Using | safe on User-Submitted Content

The problem: {{ user_input | safe }} disables Jinja2’s automatic HTML escaping, allowing users to inject malicious JavaScript (XSS attacks).

The fix: Only use | safe on content your own application generates and fully controls. Never on anything a user submitted.

Mistake 8 — Not Reading Error Messages

The problem: Beginners often panic when they see a red Flask error page and give up or start randomly changing code.

The fix: Flask’s debug error pages are incredibly informative. Read the error type and message carefully — they almost always tell you exactly what went wrong and where.


Real-World Mini Project

Now let us put everything together and build a complete, styled, three-page personal website. This project uses every concept covered in this guide.

Project Overview

PageURLDescription
Home/Welcome page with your name and intro
About/aboutA short bio and skills section
Contact/contactA form that collects name, email, message

Final Project Folder Structure

my_flask_site/
├── app.py
├── requirements.txt
├── static/
│   └── css/
│       └── style.css
└── templates/
    ├── base.html
    ├── index.html
    ├── about.html
    └── contact.html

The Complete app.py

from flask import Flask, render_template, request

app = Flask(__name__)

# ─── Home Route ──────────────────────────────────────────────
@app.route('/')
def home():
    return render_template(
        'index.html',
        page_title='Home',
        name='Jane Doe',
        tagline='Python Developer & Flask Enthusiast'
    )

# ─── About Route ─────────────────────────────────────────────
@app.route('/about')
def about():
    skills = ['Python', 'Flask', 'HTML & CSS', 'SQL', 'Git']
    return render_template(
        'about.html',
        page_title='About',
        skills=skills
    )

# ─── Contact Route ───────────────────────────────────────────
@app.route('/contact', methods=['GET', 'POST'])
def contact():
    success = False
    submitted_name = ''

    if request.method == 'POST':
        name = request.form.get('name', '').strip()
        email = request.form.get('email', '').strip()
        message = request.form.get('message', '').strip()

        if name and email and message:
            success = True
            submitted_name = name

    return render_template(
        'contact.html',
        page_title='Contact',
        success=success,
        submitted_name=submitted_name
    )

# ─── Run the App ─────────────────────────────────────────────
if __name__ == '__main__':
    app.run(debug=True)

templates/base.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{% block title %}Jane Doe{% endblock %} | Flask Portfolio</title>
    <link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
</head>
<body>

<nav>
    <span class="brand">Jane Doe</span>
    <div class="nav-links">
        <a href="{{ url_for('home') }}">Home</a>
        <a href="{{ url_for('about') }}">About</a>
        <a href="{{ url_for('contact') }}">Contact</a>
    </div>
</nav>

<main>
    {% block content %}{% endblock %}
</main>

<footer>
    <p>Built with ❤️ using Python & Flask &copy; 2026</p>
</footer>

</body>
</html>

templates/index.html

{% extends "base.html" %}

{% block title %}Home{% endblock %}

{% block content %}
<section class="hero">
    <h1>Hi, I'm {{ name }} 👋</h1>
    <p class="tagline">{{ tagline }}</p>
    <p>Welcome to my personal website, built entirely with Python and Flask.</p>
    <a href="{{ url_for('contact') }}" class="btn">Get In Touch</a>
</section>
{% endblock %}

templates/about.html

{% extends "base.html" %}

{% block title %}About{% endblock %}

{% block content %}
<h1>About Me</h1>
<p>I'm a Python developer who loves building web applications with Flask.
   I enjoy turning ideas into real, working software.</p>

<h2>My Skills</h2>
<ul class="skills-list">
    {% for skill in skills %}
        <li>✅ {{ skill }}</li>
    {% endfor %}
</ul>
{% endblock %}

templates/contact.html

{% extends "base.html" %}

{% block title %}Contact{% endblock %}

{% block content %}
<h1>Get In Touch</h1>

{% if success %}
    <div class="success-message">
        <p>✅ Thank you, {{ submitted_name }}! Your message has been received.</p>
    </div>
{% endif %}

<form action="{{ url_for('contact') }}" method="POST">
    <div class="form-group">
        <label for="name">Your Name</label>
        <input type="text" id="name" name="name" required placeholder="Jane Smith">
    </div>
    <div class="form-group">
        <label for="email">Email Address</label>
        <input type="email" id="email" name="email" required placeholder="jane@example.com">
    </div>
    <div class="form-group">
        <label for="message">Your Message</label>
        <textarea id="message" name="message" rows="6" required
                  placeholder="What would you like to discuss?"></textarea>
    </div>
    <button type="submit">Send Message 📨</button>
</form>
{% endblock %}

Running the Completed Project

# Make sure your virtual environment is active
flask run

Visit http://127.0.0.1:5000 and you will see your complete three-page personal website — styled, navigable, and with a working contact form.

🚀 What’s Next? Once your contact form is collecting submissions, you can automatically send them to your inbox using Python. This guide on how to send emails automatically using Python shows you exactly how to wire up email notifications with your Flask contact form — a genuinely useful upgrade that makes your site production-ready.

You can also persist contact form submissions by saving them to a text or CSV file. If you are not familiar with file operations in Python, this guide on how to read and write text files in Python is the perfect starting point before moving to a full database.


Frequently Asked Questions

Is Flask good for beginners?

Yes — Flask is one of the best web frameworks for Python beginners. It has a minimal learning curve, uses straightforward Python syntax, and lets you build a working website with very little boilerplate code. Unlike Django, Flask does not force you to learn ten concepts at once.

What is the difference between Flask and Django?

Flask is a micro-framework: lightweight, flexible, and minimal. You choose what to add (databases, authentication, etc.) as you need them. Django is a full-stack framework with everything built in — an ORM, admin panel, authentication system, and more. Flask is better for learning and small-to-medium projects; Django suits larger, content-heavy platforms.

Do I need to know HTML to use Flask?

Basic HTML knowledge is helpful. Flask renders HTML through Jinja2 templates, so you will be writing HTML files. However, you can start with very simple markup and improve your HTML skills as your Flask skills grow. The two learning paths complement each other well.

Can I build a full, professional website with Flask?

Absolutely. Flask scales from a simple personal page to complex, production-grade platforms. With extensions like Flask-SQLAlchemy (database), Flask-Login (user sessions), and Flask-RESTful (APIs), Flask can power sophisticated web applications.

How do I store user data in a Flask app?

For simple projects, you can save data to text or CSV files using Python’s built-in file handling. For production apps, use SQLite with Flask-SQLAlchemy (beginner-friendly) or PostgreSQL (production databases). Start simple and add complexity as your needs grow.

What Python version should I use with Flask?

Python 3.11 or later is recommended as of 2026. Flask dropped Python 2 support in Flask 2.0, and Python 3.11 delivers significant performance improvements. You can check the Flask changelog for current version compatibility details.

How do I deploy a Flask app to the internet?

Popular beginner-friendly deployment platforms include:

  • Railway — simple Git-based deployment
  • Render — free tier available, great for Flask
  • PythonAnywhere — Python-specific hosting, beginner-friendly
  • DigitalOcean App Platform — deploy directly from GitHub

What is if __name__ == '__main__' in Flask?

This Python pattern means: “only run this code if this file is executed directly (not imported by another module).” It prevents the Flask development server from starting accidentally when your app.py is imported in tests or by a production server like Gunicorn.


Conclusion

You have just covered everything a beginner needs to go from “I know Python” to “I can build real websites.” Let us recap the complete journey:

  • ✅ Understood what Flask is and why it is perfect for beginners
  • ✅ Set up a clean development environment with virtual environments
  • ✅ Built and ran your first Flask application
  • ✅ Mastered routing, dynamic URLs, and HTTP methods
  • ✅ Created dynamic HTML pages with Jinja2 templates and template inheritance
  • ✅ Handled real user input with HTML forms and Flask’s request object
  • ✅ Served CSS and images using Flask’s static file system
  • ✅ Organized your project with a clean, scalable folder structure
  • ✅ Learned the most common beginner mistakes — and how to avoid them
  • ✅ Built a complete three-page personal website with a working contact form

Flask rewards curiosity. Every concept you have learned here opens a door to something more powerful: connect a database, add user login, build a REST API, or deploy your site for the world to see.

The best next step is not just reading more — it is building. Take the mini project from this guide, make it your own, and keep adding features one at a time. That is how real developers grow.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *