Web Development ~20 min read

Building a Recipe Sharing Application

A full-stack walkthrough covering architecture decisions, backend API design, database modelling, and deployment strategies for a recipe sharing platform.

Overview Prerequisites Architecture Tutorial Key learnings Resources

Overview

A recipe sharing application is a great full-stack project because it touches every layer of a modern web app: user authentication, CRUD operations, file uploads (for recipe images), search and filtering, and a consumable API.

This guide walks through the decisions made at each stage — why certain patterns were chosen, what trade-offs exist, and how the pieces connect. You will end up with a working application you can extend and deploy.

Prerequisites

Python 3.10+ Basic HTML / CSS Understanding of REST APIs SQL basics (PostgreSQL or SQLite) Git installed

Architecture

Frontend
Browser (HTML/CSS/JS)
→ HTTP →
Flask / FastAPI backend
Backend
REST API routes
SQLite / PostgreSQL
+
File storage (images)
Auth
Session tokens (JWT)
Users table
Deploy
Docker container
Cloud host (Render / Railway)

Images are stored as files on the server (or S3 in production) with only the file path saved in the database.

Tutorial steps

1

Plan your data model

Before writing any code, sketch the database schema. A recipe app needs at minimum: users, recipes, ingredients, and tags. Decide on relationships — a recipe belongs to one user, but can have many ingredients and many tags.

users → id, username, email, password_hash recipes → id, user_id (FK), title, description, image_path, created_at ingredients→ id, recipe_id (FK), name, quantity, unit tags → id, name recipe_tags→ recipe_id (FK), tag_id (FK) [junction table]
2

Set up the Flask application

Create the project structure and initialise Flask with SQLAlchemy for ORM and Flask-Login for session management.

pip install flask flask-sqlalchemy flask-login werkzeug # app/__init__.py from flask import Flask from flask_sqlalchemy import SQLAlchemy from flask_login import LoginManager db = SQLAlchemy() login_manager = LoginManager() def create_app(): app = Flask(__name__) app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///recipes.db' app.config['SECRET_KEY'] = 'your-secret-key' db.init_app(app) login_manager.init_app(app) return app
3

Define the models

Create SQLAlchemy model classes that map to your database tables. Use relationships to enable easy navigation between related records.

class Recipe(db.Model): id = db.Column(db.Integer, primary_key=True) user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False) title = db.Column(db.String(200), nullable=False) description = db.Column(db.Text) image_path = db.Column(db.String(300)) created_at = db.Column(db.DateTime, default=datetime.utcnow) ingredients = db.relationship('Ingredient', backref='recipe', lazy=True, cascade='all, delete')
4

Build the API routes

Create routes for listing, creating, updating, and deleting recipes. Use Flask's request object to handle JSON payloads and file uploads.

@app.route('/api/recipes', methods=['GET']) def get_recipes(): recipes = Recipe.query.order_by(Recipe.created_at.desc()).all() return jsonify([r.to_dict() for r in recipes]) @app.route('/api/recipes', methods=['POST']) @login_required def create_recipe(): data = request.get_json() recipe = Recipe(user_id=current_user.id, title=data['title'], description=data.get('description')) db.session.add(recipe) db.session.commit() return jsonify(recipe.to_dict()), 201
5

Handle image uploads

Allow users to upload a photo for each recipe. Validate the file type, generate a secure filename, and store it in an uploads folder. In production, swap local storage for S3.

ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif', 'webp'} def allowed_file(filename): return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS @app.route('/api/recipes/<int:id>/image', methods=['POST']) @login_required def upload_image(id): file = request.files.get('image') if file and allowed_file(file.filename): filename = secure_filename(f"{id}_{file.filename}") file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename)) recipe = Recipe.query.get_or_404(id) recipe.image_path = filename db.session.commit() return jsonify({'status': 'uploaded'})
6

Containerise and deploy

Write a Dockerfile to containerise the application so it runs consistently in any environment. Deploy to a free cloud host like Render or Railway by connecting your GitHub repo.

FROM python:3.11-slim WORKDIR /app COPY requirements.txt . RUN pip install -r requirements.txt COPY . . EXPOSE 5000 CMD ["gunicorn", "--bind", "0.0.0.0:5000", "app:create_app()"]

Key learnings

Always hash passwords with werkzeug.security or bcrypt — never store plain text passwords.
Use cascade='all, delete' on relationships so deleting a recipe automatically deletes its ingredients.
Validate and sanitise file uploads server-side. Client-side validation is for UX only, not security.
Separate your business logic from your route handlers early — it makes testing much easier as the app grows.
Use environment variables for secrets — never hardcode database URLs or API keys in source code.

Resources