Prerequisites
- Basic understanding of programming concepts ๐
- Python installation (3.8+) ๐
- VS Code or preferred IDE ๐ป
What you'll learn
- Understand the concept fundamentals ๐ฏ
- Apply the concept in real projects ๐๏ธ
- Debug common issues ๐
- Write clean, Pythonic code โจ
๐ฏ Introduction
Welcome to this exciting tutorial on building full-stack applications with Python! ๐ In this guide, weโll explore how to create complete web applications that handle everything from databases to beautiful user interfaces.
Youโll discover how full-stack development can transform your Python skills into powerful web applications. Whether youโre building e-commerce platforms ๐, social networks ๐ฅ, or productivity tools ๐, understanding full-stack development is essential for creating complete, production-ready applications.
By the end of this tutorial, youโll feel confident building your own full-stack applications! Letโs dive in! ๐โโ๏ธ
๐ Understanding Full-Stack Development
๐ค What is Full-Stack Development?
Full-stack development is like building a complete restaurant ๐ด. Think of it as creating everything from the kitchen (backend) to the dining room (frontend) that works together to serve delicious experiences to your customers (users).
In Python terms, full-stack development means building:
- โจ Backend API with Flask/Django
- ๐ Database layer with SQLAlchemy/Django ORM
- ๐ก๏ธ Authentication and security
- ๐จ Frontend interface (often with JavaScript frameworks)
- ๐ฆ Deployment and hosting
๐ก Why Build Full-Stack Applications?
Hereโs why developers love full-stack development:
- Complete Control ๐: Design entire user experiences
- Better Understanding ๐ป: Know how all pieces fit together
- Rapid Prototyping ๐: Build MVPs quickly
- Career Flexibility ๐ง: Work on any part of the stack
Real-world example: Imagine building an online bookstore ๐. With full-stack skills, you can create the book catalog, shopping cart, user accounts, and payment processing all by yourself!
๐ง Basic Architecture
๐ Simple Full-Stack Structure
Letโs start with a basic full-stack application structure:
# ๐ Hello, Full-Stack!
# project_structure.py
"""
my_bookstore/
โโโ backend/
โ โโโ app.py # ๐ Flask application
โ โโโ models.py # ๐ Database models
โ โโโ auth.py # ๐ Authentication
โ โโโ api/
โ โโโ books.py # ๐ Book endpoints
โ โโโ users.py # ๐ค User endpoints
โโโ frontend/
โ โโโ index.html # ๐ Home page
โ โโโ app.js # ๐ฎ JavaScript logic
โ โโโ styles.css # ๐จ Styling
โโโ database/
โโโ schema.sql # ๐๏ธ Database structure
"""
๐ฏ Backend Setup with Flask
Hereโs a complete backend setup:
# ๐๏ธ backend/app.py
from flask import Flask, jsonify, request
from flask_cors import CORS
from flask_sqlalchemy import SQLAlchemy
from flask_jwt_extended import JWTManager, create_access_token
import os
# ๐จ Create Flask app
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///bookstore.db'
app.config['JWT_SECRET_KEY'] = 'super-secret-key' # ๐ Change in production!
# ๐ ๏ธ Initialize extensions
db = SQLAlchemy(app)
jwt = JWTManager(app)
CORS(app) # ๐ Enable cross-origin requests
# ๐ Database Models
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80), unique=True, nullable=False)
email = db.Column(db.String(120), unique=True, nullable=False)
password_hash = db.Column(db.String(128))
def to_dict(self):
return {
'id': self.id,
'username': self.username,
'email': self.email
}
class Book(db.Model):
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(200), nullable=False)
author = db.Column(db.String(100), nullable=False)
price = db.Column(db.Float, nullable=False)
stock = db.Column(db.Integer, default=0)
def to_dict(self):
return {
'id': self.id,
'title': self.title,
'author': self.author,
'price': self.price,
'stock': self.stock
}
# ๐ API Routes
@app.route('/api/books', methods=['GET'])
def get_books():
"""Get all books ๐"""
books = Book.query.all()
return jsonify([book.to_dict() for book in books])
@app.route('/api/books/<int:book_id>', methods=['GET'])
def get_book(book_id):
"""Get a specific book ๐"""
book = Book.query.get_or_404(book_id)
return jsonify(book.to_dict())
@app.route('/api/auth/register', methods=['POST'])
def register():
"""Register new user ๐ค"""
data = request.get_json()
# ๐ก๏ธ Check if user exists
if User.query.filter_by(username=data['username']).first():
return jsonify({'error': 'Username already exists'}), 400
# โจ Create new user
user = User(
username=data['username'],
email=data['email'],
password_hash=data['password'] # ๐ Hash this in production!
)
db.session.add(user)
db.session.commit()
# ๐ Create access token
access_token = create_access_token(identity=user.id)
return jsonify({
'message': 'User created successfully!',
'access_token': access_token,
'user': user.to_dict()
}), 201
๐ก Practical Examples
๐ Example 1: Complete Shopping Cart API
Letโs build a full shopping cart system:
# ๐๏ธ backend/api/cart.py
from flask import Blueprint, jsonify, request
from flask_jwt_extended import jwt_required, get_jwt_identity
from datetime import datetime
cart_bp = Blueprint('cart', __name__)
# ๐ Shopping cart storage (use Redis in production!)
shopping_carts = {}
class ShoppingCart:
def __init__(self, user_id):
self.user_id = user_id
self.items = []
self.created_at = datetime.now()
def add_item(self, book_id, quantity=1):
"""โ Add item to cart"""
# ๐ Check if item already in cart
for item in self.items:
if item['book_id'] == book_id:
item['quantity'] += quantity
return
# ๐ Add new item
self.items.append({
'book_id': book_id,
'quantity': quantity,
'added_at': datetime.now().isoformat()
})
def remove_item(self, book_id):
"""โ Remove item from cart"""
self.items = [item for item in self.items if item['book_id'] != book_id]
def get_total(self, books_db):
"""๐ฐ Calculate total price"""
total = 0
for item in self.items:
book = books_db.get(item['book_id'])
if book:
total += book['price'] * item['quantity']
return round(total, 2)
def to_dict(self):
"""๐ Convert to dictionary"""
return {
'user_id': self.user_id,
'items': self.items,
'item_count': sum(item['quantity'] for item in self.items),
'created_at': self.created_at.isoformat()
}
@cart_bp.route('/api/cart', methods=['GET'])
@jwt_required()
def get_cart():
"""๐ Get user's cart"""
user_id = get_jwt_identity()
cart = shopping_carts.get(user_id, ShoppingCart(user_id))
return jsonify(cart.to_dict())
@cart_bp.route('/api/cart/add', methods=['POST'])
@jwt_required()
def add_to_cart():
"""โ Add item to cart"""
user_id = get_jwt_identity()
data = request.get_json()
# ๐ Get or create cart
if user_id not in shopping_carts:
shopping_carts[user_id] = ShoppingCart(user_id)
cart = shopping_carts[user_id]
cart.add_item(data['book_id'], data.get('quantity', 1))
return jsonify({
'message': 'Item added to cart! ๐',
'cart': cart.to_dict()
})
@cart_bp.route('/api/cart/checkout', methods=['POST'])
@jwt_required()
def checkout():
"""๐ณ Checkout process"""
user_id = get_jwt_identity()
cart = shopping_carts.get(user_id)
if not cart or not cart.items:
return jsonify({'error': 'Cart is empty! ๐'}), 400
# ๐ฏ Here you would:
# 1. Validate inventory
# 2. Process payment
# 3. Create order
# 4. Send confirmation email
order = {
'order_id': f"ORD-{datetime.now().strftime('%Y%m%d%H%M%S')}",
'user_id': user_id,
'items': cart.items,
'total': cart.get_total(books_db),
'status': 'confirmed',
'created_at': datetime.now().isoformat()
}
# ๐งน Clear cart
del shopping_carts[user_id]
return jsonify({
'message': 'Order placed successfully! ๐',
'order': order
})
๐ฎ Example 2: Frontend with Vue.js
Letโs create an interactive frontend:
<!-- ๐ frontend/index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>๐ Python Bookstore</title>
<script src="https://cdn.jsdelivr.net/npm/vue@3"></script>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<link href="https://cdn.jsdelivr.net/npm/tailwindcss@2/dist/tailwind.min.css" rel="stylesheet">
</head>
<body>
<div id="app" class="container mx-auto p-4">
<!-- ๐ฏ Header -->
<header class="mb-8">
<h1 class="text-4xl font-bold text-center mb-4">
๐ Python Bookstore
</h1>
<div class="flex justify-between items-center">
<span v-if="user" class="text-lg">
๐ Welcome, {{ user.username }}!
</span>
<button v-else @click="showLogin = true"
class="bg-blue-500 text-white px-4 py-2 rounded">
๐ Login
</button>
<div v-if="user" class="flex items-center gap-4">
<span class="text-lg">
๐ Cart ({{ cartItemCount }})
</span>
<button @click="logout"
class="bg-red-500 text-white px-4 py-2 rounded">
๐ช Logout
</button>
</div>
</div>
</header>
<!-- ๐ Book Grid -->
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
<div v-for="book in books" :key="book.id"
class="border rounded-lg p-4 shadow-lg">
<h3 class="text-xl font-semibold mb-2">{{ book.title }}</h3>
<p class="text-gray-600 mb-2">โ๏ธ {{ book.author }}</p>
<p class="text-2xl font-bold mb-4">${{ book.price }}</p>
<p class="text-sm text-gray-500 mb-4">
๐ฆ {{ book.stock }} in stock
</p>
<button @click="addToCart(book)"
:disabled="book.stock === 0"
class="bg-green-500 text-white px-4 py-2 rounded w-full"
:class="{ 'opacity-50': book.stock === 0 }">
{{ book.stock === 0 ? 'โ Out of Stock' : '๐ Add to Cart' }}
</button>
</div>
</div>
<!-- ๐ Shopping Cart Modal -->
<div v-if="showCart" class="fixed inset-0 bg-black bg-opacity-50
flex items-center justify-center">
<div class="bg-white p-6 rounded-lg max-w-2xl w-full">
<h2 class="text-2xl font-bold mb-4">๐ Your Cart</h2>
<div v-if="cart.items.length === 0" class="text-center py-8">
<p class="text-gray-500">Your cart is empty! ๐ข</p>
</div>
<div v-else>
<div v-for="item in cartWithDetails" :key="item.book_id"
class="flex justify-between items-center mb-4">
<div>
<h4 class="font-semibold">{{ item.title }}</h4>
<p class="text-gray-600">
${{ item.price }} x {{ item.quantity }}
</p>
</div>
<div class="flex items-center gap-2">
<span class="font-bold">
${{ (item.price * item.quantity).toFixed(2) }}
</span>
<button @click="removeFromCart(item.book_id)"
class="text-red-500">
โ
</button>
</div>
</div>
<hr class="my-4">
<div class="text-xl font-bold text-right">
Total: ${{ cartTotal.toFixed(2) }}
</div>
<button @click="checkout"
class="bg-blue-500 text-white px-6 py-3
rounded w-full mt-4">
๐ณ Checkout
</button>
</div>
<button @click="showCart = false"
class="mt-4 text-gray-500 underline">
Close
</button>
</div>
</div>
</div>
<script>
const { createApp } = Vue;
createApp({
data() {
return {
books: [],
user: null,
cart: { items: [] },
showCart: false,
showLogin: false,
apiUrl: 'http://localhost:5000/api',
token: localStorage.getItem('token')
}
},
computed: {
cartItemCount() {
return this.cart.items.reduce((sum, item) =>
sum + item.quantity, 0);
},
cartWithDetails() {
return this.cart.items.map(item => {
const book = this.books.find(b => b.id === item.book_id);
return { ...item, ...book };
});
},
cartTotal() {
return this.cartWithDetails.reduce((sum, item) =>
sum + (item.price * item.quantity), 0);
}
},
methods: {
async fetchBooks() {
try {
const response = await axios.get(`${this.apiUrl}/books`);
this.books = response.data;
console.log('๐ Books loaded!');
} catch (error) {
console.error('โ Error loading books:', error);
}
},
async addToCart(book) {
if (!this.user) {
alert('Please login first! ๐');
this.showLogin = true;
return;
}
try {
const response = await axios.post(
`${this.apiUrl}/cart/add`,
{ book_id: book.id },
{ headers: { Authorization: `Bearer ${this.token}` }}
);
this.cart = response.data.cart;
console.log('โ
Added to cart!');
} catch (error) {
console.error('โ Error adding to cart:', error);
}
},
async checkout() {
try {
const response = await axios.post(
`${this.apiUrl}/cart/checkout`,
{},
{ headers: { Authorization: `Bearer ${this.token}` }}
);
alert(`๐ Order placed! ${response.data.order.order_id}`);
this.cart = { items: [] };
this.showCart = false;
} catch (error) {
console.error('โ Checkout error:', error);
}
},
logout() {
this.user = null;
this.token = null;
localStorage.removeItem('token');
this.cart = { items: [] };
}
},
mounted() {
this.fetchBooks();
// ๐ Check if user is logged in
if (this.token) {
// Validate token and load user data
console.log('๐ User authenticated');
}
}
}).mount('#app');
</script>
</body>
</html>
๐ Advanced Concepts
๐งโโ๏ธ Real-time Features with WebSockets
When youโre ready to level up, add real-time features:
# ๐ฏ Real-time notifications with Flask-SocketIO
from flask_socketio import SocketIO, emit, join_room
socketio = SocketIO(app, cors_allowed_origins="*")
# ๐ฆ Order tracking in real-time
@socketio.on('track_order')
def handle_order_tracking(data):
"""๐ Track order in real-time"""
order_id = data['order_id']
room = f"order_{order_id}"
join_room(room)
# ๐ Send initial status
emit('order_status', {
'order_id': order_id,
'status': 'processing',
'message': '๐ฆ Your order is being prepared!'
})
# ๐ฏ Simulate order updates
def update_order_status():
statuses = [
('packed', '๐ฆ Order packed and ready!'),
('shipped', '๐ Order shipped!'),
('out_for_delivery', '๐ Out for delivery!'),
('delivered', 'โ
Delivered! Enjoy your books! ๐')
]
for status, message in statuses:
socketio.sleep(5) # Simulate delay
socketio.emit('order_status', {
'order_id': order_id,
'status': status,
'message': message
}, room=room)
# ๐ Start background task
socketio.start_background_task(update_order_status)
๐๏ธ Microservices Architecture
For large applications, consider microservices:
# ๐ Microservices approach
"""
bookstore_microservices/
โโโ api_gateway/ # ๐ Main entry point
โโโ auth_service/ # ๐ Authentication
โโโ catalog_service/ # ๐ Book catalog
โโโ cart_service/ # ๐ Shopping cart
โโโ order_service/ # ๐ฆ Order processing
โโโ notification_service/ # ๐ง Email/SMS
โโโ payment_service/ # ๐ณ Payment processing
"""
# ๐ฏ Example: Catalog Service
# catalog_service/app.py
from flask import Flask, jsonify
import requests
app = Flask(__name__)
@app.route('/books/<int:book_id>/recommendations')
def get_recommendations(book_id):
"""๐ฏ Get book recommendations using ML service"""
# ๐ค Call ML recommendation service
ml_response = requests.get(
f'http://ml-service:5001/recommend/{book_id}'
)
recommendations = ml_response.json()
# ๐ Get book details for recommendations
book_ids = recommendations['book_ids']
books = Book.query.filter(Book.id.in_(book_ids)).all()
return jsonify({
'recommendations': [book.to_dict() for book in books],
'reason': recommendations['reason']
})
โ ๏ธ Common Pitfalls and Solutions
๐ฑ Pitfall 1: CORS Issues
# โ Wrong way - forgetting CORS!
@app.route('/api/data')
def get_data():
return jsonify({'data': 'Hello'})
# ๐ฅ Frontend can't access this!
# โ
Correct way - enable CORS!
from flask_cors import CORS
app = Flask(__name__)
CORS(app, origins=['http://localhost:3000']) # ๐ก๏ธ Specific origins
# Or for specific routes
from flask_cors import cross_origin
@app.route('/api/data')
@cross_origin()
def get_data():
return jsonify({'data': 'Hello'}) # โ
Frontend can access!
๐คฏ Pitfall 2: Not handling async operations
# โ Dangerous - blocking operations!
@app.route('/api/send-email')
def send_email():
# ๐ฅ This blocks the entire app!
time.sleep(10) # Simulating email sending
return jsonify({'status': 'sent'})
# โ
Safe - use background tasks!
from celery import Celery
celery = Celery(app.name, broker='redis://localhost:6379')
@celery.task
def send_email_async(recipient, subject, body):
"""๐ง Send email in background"""
# Email sending logic here
return True
@app.route('/api/send-email')
def send_email():
# ๐ Non-blocking!
task = send_email_async.delay(
'[email protected]',
'Order Confirmation',
'Your order is confirmed!'
)
return jsonify({
'status': 'queued',
'task_id': task.id
})
๐ ๏ธ Best Practices
- ๐ฏ API Design: Follow RESTful principles consistently
- ๐ Documentation: Use OpenAPI/Swagger for API docs
- ๐ก๏ธ Security First: Always validate input and use JWT tokens
- ๐จ Separation of Concerns: Keep frontend and backend loosely coupled
- โจ Error Handling: Provide meaningful error messages
๐งช Hands-On Exercise
๐ฏ Challenge: Build a Book Review System
Add a review system to our bookstore:
๐ Requirements:
- โ Users can rate books (1-5 stars)
- ๐ท๏ธ Users can write text reviews
- ๐ค Show reviewer information
- ๐ Display review dates
- ๐จ Calculate average ratings
๐ Bonus Points:
- Add โhelpfulโ voting on reviews
- Implement review moderation
- Create review statistics dashboard
๐ก Solution
๐ Click to see solution
# ๐ฏ Complete review system!
# backend/models/review.py
from datetime import datetime
from sqlalchemy import func
class Review(db.Model):
id = db.Column(db.Integer, primary_key=True)
book_id = db.Column(db.Integer, db.ForeignKey('book.id'), nullable=False)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
rating = db.Column(db.Integer, nullable=False) # 1-5 stars
title = db.Column(db.String(100))
content = db.Column(db.Text)
helpful_count = db.Column(db.Integer, default=0)
created_at = db.Column(db.DateTime, default=datetime.utcnow)
# ๐ Relationships
book = db.relationship('Book', backref='reviews')
user = db.relationship('User', backref='reviews')
def to_dict(self):
return {
'id': self.id,
'book_id': self.book_id,
'user': self.user.username,
'rating': self.rating,
'title': self.title,
'content': self.content,
'helpful_count': self.helpful_count,
'created_at': self.created_at.isoformat()
}
# ๐ Add to Book model
class Book(db.Model):
# ... existing fields ...
@property
def average_rating(self):
"""โญ Calculate average rating"""
avg = db.session.query(func.avg(Review.rating))\
.filter(Review.book_id == self.id).scalar()
return round(avg, 1) if avg else 0
@property
def review_count(self):
"""๐ Get total reviews"""
return Review.query.filter_by(book_id=self.id).count()
# ๐ API endpoints
@app.route('/api/books/<int:book_id>/reviews', methods=['GET'])
def get_reviews(book_id):
"""๐ Get all reviews for a book"""
reviews = Review.query.filter_by(book_id=book_id)\
.order_by(Review.created_at.desc()).all()
return jsonify({
'reviews': [review.to_dict() for review in reviews],
'average_rating': Book.query.get(book_id).average_rating,
'total_reviews': len(reviews)
})
@app.route('/api/books/<int:book_id>/reviews', methods=['POST'])
@jwt_required()
def add_review(book_id):
"""โ๏ธ Add a new review"""
user_id = get_jwt_identity()
data = request.get_json()
# ๐ก๏ธ Check if user already reviewed
existing = Review.query.filter_by(
book_id=book_id,
user_id=user_id
).first()
if existing:
return jsonify({'error': 'You already reviewed this book!'}), 400
# โจ Create new review
review = Review(
book_id=book_id,
user_id=user_id,
rating=data['rating'],
title=data.get('title', ''),
content=data.get('content', '')
)
db.session.add(review)
db.session.commit()
return jsonify({
'message': 'Review added successfully! ๐',
'review': review.to_dict()
}), 201
@app.route('/api/reviews/<int:review_id>/helpful', methods=['POST'])
@jwt_required()
def mark_helpful(review_id):
"""๐ Mark review as helpful"""
review = Review.query.get_or_404(review_id)
review.helpful_count += 1
db.session.commit()
return jsonify({
'message': 'Thanks for your feedback! ๐',
'helpful_count': review.helpful_count
})
# ๐ Statistics endpoint
@app.route('/api/books/<int:book_id>/review-stats')
def get_review_stats(book_id):
"""๐ Get review statistics"""
# ๐ฏ Rating distribution
rating_dist = db.session.query(
Review.rating,
func.count(Review.id)
).filter(Review.book_id == book_id)\
.group_by(Review.rating).all()
distribution = {str(i): 0 for i in range(1, 6)}
for rating, count in rating_dist:
distribution[str(rating)] = count
return jsonify({
'average_rating': Book.query.get(book_id).average_rating,
'total_reviews': Book.query.get(book_id).review_count,
'rating_distribution': distribution,
'stats': {
'5_star_percentage': round(distribution['5'] / max(sum(distribution.values()), 1) * 100),
'recommended': distribution['4'] + distribution['5'] > distribution['1'] + distribution['2']
}
})
๐ Key Takeaways
Youโve learned so much! Hereโs what you can now do:
- โ Create full-stack applications with Python backend and modern frontend ๐ช
- โ Design RESTful APIs that are secure and scalable ๐ก๏ธ
- โ Handle authentication with JWT tokens ๐ฏ
- โ Build real-time features with WebSockets ๐
- โ Deploy complete applications to production! ๐
Remember: Full-stack development is about connecting all the pieces to create amazing user experiences! ๐ค
๐ค Next Steps
Congratulations! ๐ Youโve mastered full-stack development with Python!
Hereโs what to do next:
- ๐ป Build your own full-stack project from scratch
- ๐๏ธ Add more features like payment integration
- ๐ Learn about Docker for containerization
- ๐ Deploy your application to the cloud!
Remember: Every successful web application started with a single line of code. Keep building, keep learning, and most importantly, have fun creating amazing web applications! ๐
Happy coding! ๐๐โจ