Skip to content

Latest commit

Β 

History

5 Commits

Folders and files

NameName
Last commit message
Last commit date
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

πŸŽ“ Employee REST API

Python Flask JWT Docker License: MIT

A production-ready Flask REST API featuring JWT authentication, comprehensive Student CRUD operations, and a clean Repository Pattern architecture designed for seamless extensibility from JSON file storage to any database backend.

Built with industry-standard patterns: Application Factory, Blueprints, Service Layer, Repository Abstraction, Docker, and GitHub Actions CI/CD.


πŸ“‘ Table of Contents


✨ Features

Category Features
Authentication JWT-based auth with access & refresh tokens, secure password hashing (PBKDF2-SHA256), role-based user management
Student Management Full CRUD operations, email uniqueness validation, enrollment tracking, active/inactive status
Architecture Repository Pattern, Service Layer, Application Factory, Blueprint-based routing
API Design RESTful endpoints, versioned API (/api/v1), consistent JSON responses, comprehensive error handling
DevOps Docker multi-stage builds, Docker Compose support, GitHub Actions CI/CD, health checks
Testing 12+ automated tests, pytest with coverage reports, isolated test fixtures
Security Non-root Docker user, token expiration, input validation, error sanitization

πŸ“ Project Structure

RestApiGithub/
β”‚
β”œβ”€β”€ app/                              # ── Application Package ──
β”‚   β”œβ”€β”€ __init__.py                   # Application factory (create_app)
β”‚   β”œβ”€β”€ config.py                     # Environment-based config (dev/test/prod)
β”‚   β”œβ”€β”€ extensions.py                 # Flask extension instances (JWT)
β”‚   β”œβ”€β”€ errors.py                     # Global error handlers (400, 401, 404, 500)
β”‚   β”‚
β”‚   β”œβ”€β”€ api/                          # ── API Layer (Blueprints) ──
β”‚   β”‚   β”œβ”€β”€ __init__.py
β”‚   β”‚   β”œβ”€β”€ health.py                 # GET  /api/v1/health
β”‚   β”‚   β”œβ”€β”€ auth.py                   # POST /api/v1/auth/register & login
β”‚   β”‚   └── students.py               # CRUD /api/v1/students
β”‚   β”‚
β”‚   β”œβ”€β”€ models/                       # ── Data Models ──
β”‚   β”‚   β”œβ”€β”€ __init__.py
β”‚   β”‚   β”œβ”€β”€ user.py                   # User dataclass (id, username, password_hash, role)
β”‚   β”‚   └── student.py                # Student dataclass (id, name, email, course, etc.)
β”‚   β”‚
β”‚   β”œβ”€β”€ repositories/                 # ── Data Access Layer ──
β”‚   β”‚   β”œβ”€β”€ __init__.py
β”‚   β”‚   β”œβ”€β”€ base_repository.py        # Abstract interface (swap JSON β†’ DB here)
β”‚   β”‚   └── json_repository.py        # JSON file-backed implementation
β”‚   β”‚
β”‚   └── services/                     # ── Business Logic Layer ──
β”‚       β”œβ”€β”€ __init__.py
β”‚       β”œβ”€β”€ auth_service.py           # Register, login, JWT token generation
β”‚       └── student_service.py        # Student CRUD operations
β”‚
β”œβ”€β”€ tests/                            # ── Test Suite (12 tests) ──
β”‚   β”œβ”€β”€ conftest.py                   # Shared fixtures (app, client, auth_headers)
β”‚   β”œβ”€β”€ test_health.py                # Health endpoint test
β”‚   β”œβ”€β”€ test_auth.py                  # Auth endpoint tests (5 tests)
β”‚   └── test_students.py              # Student endpoint tests (6 tests)
β”‚
β”œβ”€β”€ data/                             # ── Runtime JSON Data Store (git-ignored) ──
β”‚   β”œβ”€β”€ users.json                    # Created on first user registration
β”‚   └── students.json                 # Created on first student creation
β”‚
β”œβ”€β”€ .github/
β”‚   └── workflows/
β”‚       └── ci.yml                    # GitHub Actions: Lint β†’ Test β†’ Zip β†’ Docker
β”‚
β”œβ”€β”€ Dockerfile                        # Multi-stage production build (gunicorn)
β”œβ”€β”€ .dockerignore                     # Files excluded from Docker build
β”œβ”€β”€ docker-compose.yml                # One-command Docker deployment
β”œβ”€β”€ requirements.txt                  # Python dependencies
β”œβ”€β”€ pytest.ini                        # Pytest configuration
β”œβ”€β”€ run.py                            # Development entry point
β”œβ”€β”€ wsgi.py                           # Production WSGI entry (gunicorn)
β”œβ”€β”€ test_api_manual.py                # Manual API test script
β”œβ”€β”€ .env.example                      # Environment variable template
β”œβ”€β”€ .gitignore                        # Git ignore rules
└── README.md                         # This file

πŸ—οΈ Architecture

The project follows the Repository Pattern with clear separation of concerns:

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚   API Layer      β”‚     β”‚  Service Layer   β”‚     β”‚ Repository Layer β”‚     β”‚   Data Store     β”‚
β”‚  (Blueprints)    β”‚ ──▢ β”‚ (Business Logic) β”‚ ──▢ β”‚   (Interface)    β”‚ ──▢ β”‚  (JSON / DB)     β”‚
β”‚                  β”‚     β”‚                  β”‚     β”‚                  β”‚     β”‚                  β”‚
β”‚ health.py        β”‚     β”‚ auth_service.py  β”‚     β”‚ base_repository  β”‚     β”‚ users.json       β”‚
β”‚ auth.py          β”‚     β”‚ student_service  β”‚     β”‚ json_repository  β”‚     β”‚ students.json    β”‚
β”‚ students.py      β”‚     β”‚                  β”‚     β”‚                  β”‚     β”‚                  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Why this pattern?

Layer Responsibility Benefit
API Layer HTTP request/response handling, input validation Thin controllers, easy to test
Service Layer Business logic, data transformation Reusable across different interfaces
Repository Layer Data access abstraction Swap data stores without code changes
Data Store Persistence (JSON/DB) Flexible storage options

Request Flow

Client Request
      β”‚
      β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                     Flask Application                        β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”      β”‚
β”‚  β”‚   JWT       β”‚    β”‚   Error     β”‚    β”‚   Config    β”‚      β”‚
β”‚  β”‚   Auth      β”‚    β”‚   Handlers  β”‚    β”‚   Manager   β”‚      β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜      β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
      β”‚
      β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                    Blueprint (API Layer)                     β”‚
β”‚  β€’ Route definitions                                         β”‚
β”‚  β€’ Request parsing                                           β”‚
β”‚  β€’ Response formatting                                       β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
      β”‚
      β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                   Service Layer                              β”‚
β”‚  β€’ Business logic                                            β”‚
β”‚  β€’ Validation rules                                          β”‚
β”‚  β€’ Token generation                                          β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
      β”‚
      β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                  Repository Layer                            β”‚
β”‚  β€’ CRUD operations                                           β”‚
β”‚  β€’ Data serialization                                        β”‚
β”‚  β€’ Thread-safe operations                                    β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
      β”‚
      β–Ό
   Data Store
   (JSON/Database)

πŸ› οΈ Tech Stack

Category Technology Version Purpose
Runtime Python 3.10+ Programming language
Framework Flask 3.1.x Web framework
Authentication Flask-JWT-Extended 4.7.x JWT token handling
Security Werkzeug 3.1.x Password hashing
Production Server Gunicorn 23.0.x WSGI HTTP server
Testing pytest 8.3.x Test framework
Coverage pytest-cov 6.0.x Code coverage
Linting flake8 7.1.x Code quality
Containerization Docker Latest Container runtime
Orchestration Docker Compose 3.9 Multi-container apps

πŸš€ Getting Started

Prerequisites

Requirement Version Required
Python 3.10 or higher βœ… Yes
pip Latest βœ… Yes
Docker Latest ⚑ Optional
Docker Compose v2+ ⚑ Optional
Git Latest ⚑ Optional

1. Local Development

# Clone the repository
git clone https://github.com/<your-username>/RestApiGithub.git
cd RestApiGithub

# Create virtual environment
python -m venv venv

# Activate virtual environment
# Windows (PowerShell)
.\venv\Scripts\Activate.ps1
# Windows (CMD)
venv\Scripts\activate.bat
# macOS / Linux
source venv/bin/activate

# Install dependencies
pip install -r requirements.txt

# Start the development server
python run.py

The API is now running at http://localhost:5000

Verify Installation

# Check health endpoint
curl http://localhost:5000/api/v1/health

# Expected response:
# {"status":"healthy","timestamp":"2026-02-20T..."}

2. Docker Deployment

Using Docker Compose (Recommended)

# Build and start in foreground
docker-compose up --build

# Build and start in background
docker-compose up --build -d

# View logs
docker-compose logs -f

# Stop and remove containers
docker-compose down

# Stop and remove containers + volumes
docker-compose down -v

Standalone Docker

# Build the image
docker build -t student-api .

# Run the container
docker run -p 5000:5000 \
  -e SECRET_KEY=my-secret-key \
  -e JWT_SECRET_KEY=jwt-secret-key \
  -v student-data:/app/data \
  --name student-api \
  student-api

# Check logs
docker logs -f student-api

# Stop the container
docker stop student-api

# Remove the container
docker rm student-api

3. Environment Variables

Create a .env file in the project root (copy from .env.example):

cp .env.example .env
Variable Default Description Required
FLASK_ENV development Environment mode: development, testing, production No
SECRET_KEY change-me-in-production Flask secret key for session signing Yes (prod)
JWT_SECRET_KEY jwt-change-me-in-production Secret key for JWT token signing Yes (prod)
JWT_ACCESS_TOKEN_HOURS 1 Access token validity period in hours No
JWT_REFRESH_TOKEN_DAYS 30 Refresh token validity period in days No
PORT 5000 Server listening port No
DATA_DIR ./data Directory for JSON data storage No

⚠️ Security Warning: Always use strong, unique values for SECRET_KEY and JWT_SECRET_KEY in production!


πŸ” API Reference

Base URL: http://localhost:5000/api/v1

Content-Type: application/json

Health Check

Check if the API is running and healthy.

Method Endpoint Auth Required Rate Limited
GET /health ❌ No ❌ No

Response

GET /api/v1/health
{
  "status": "healthy",
  "timestamp": "2026-02-20T10:30:00+00:00"
}

Authentication

Register User

Create a new user account.

Method Endpoint Auth Required
POST /auth/register ❌ No

Request Body:

Field Type Required Description
username string βœ… Yes Unique username (min 1 char)
password string βœ… Yes User password (min 1 char)
role string ❌ No User role (default: "user")
POST /api/v1/auth/register
Content-Type: application/json

{
  "username": "admin",
  "password": "Admin123!",
  "role": "admin"
}

Success Response (201 Created):

{
  "message": "User registered successfully",
  "user": {
    "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "username": "admin",
    "role": "admin"
  }
}

Error Responses:

Status Condition Response
400 Missing username or password {"error": "username and password are required"}
409 Username already exists {"error": "Username already exists"}

Login

Authenticate and receive JWT tokens.

Method Endpoint Auth Required
POST /auth/login ❌ No

Request Body:

Field Type Required Description
username string βœ… Yes Registered username
password string βœ… Yes User password
POST /api/v1/auth/login
Content-Type: application/json

{
  "username": "admin",
  "password": "Admin123!"
}

Success Response (200 OK):

{
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "user": {
    "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "username": "admin",
    "role": "admin"
  }
}

Error Responses:

Status Condition Response
400 Missing credentials {"error": "username and password are required"}
401 Invalid credentials {"error": "Invalid username or password"}

πŸ“Œ Important: Copy the access_token from the login response. Use it in the Authorization header for all protected endpoints.


Students (Requires JWT)

All student endpoints require authentication via Bearer token:

Authorization: Bearer <access_token>

List All Students

Method Endpoint Auth Required
GET /students βœ… Yes
GET /api/v1/students
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...

Response (200 OK):

{
  "count": 2,
  "students": [
    {
      "id": "f5e6d7c8-...",
      "first_name": "Alice",
      "last_name": "Smith",
      "email": "alice@university.com",
      "course": "Computer Science",
      "enrollment_date": "2026-02-13T10:30:00+00:00",
      "is_active": true
    },
    {
      "id": "a1b2c3d4-...",
      "first_name": "Bob",
      "last_name": "Johnson",
      "email": "bob@university.com",
      "course": "Mathematics",
      "enrollment_date": "2026-02-14T09:15:00+00:00",
      "is_active": true
    }
  ]
}

Get Student by ID

Method Endpoint Auth Required
GET /students/<id> βœ… Yes
GET /api/v1/students/f5e6d7c8-1234-5678-abcd-ef1234567890
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...

Response (200 OK):

{
  "id": "f5e6d7c8-1234-5678-abcd-ef1234567890",
  "first_name": "Alice",
  "last_name": "Smith",
  "email": "alice@university.com",
  "course": "Computer Science",
  "enrollment_date": "2026-02-13T10:30:00+00:00",
  "is_active": true
}

Error Response (404 Not Found):

{
  "error": "Student not found"
}

Create Student

Method Endpoint Auth Required
POST /students βœ… Yes

Request Body:

Field Type Required Description
first_name string βœ… Yes Student's first name
last_name string βœ… Yes Student's last name
email string βœ… Yes Unique email address
course string βœ… Yes Enrolled course name
POST /api/v1/students
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
Content-Type: application/json

{
  "first_name": "Alice",
  "last_name": "Smith",
  "email": "alice@university.com",
  "course": "Computer Science"
}

Response (201 Created):

{
  "message": "Student created",
  "student": {
    "id": "f5e6d7c8-1234-5678-abcd-ef1234567890",
    "first_name": "Alice",
    "last_name": "Smith",
    "email": "alice@university.com",
    "course": "Computer Science",
    "enrollment_date": "2026-02-20T10:30:00+00:00",
    "is_active": true
  }
}

Error Responses:

Status Condition Response
400 Missing required fields {"error": "Missing fields: first_name, email"}
409 Email already exists {"error": "A student with this email already exists"}

Update Student

Method Endpoint Auth Required
PUT /students/<id> βœ… Yes

Request Body (all fields optional):

Field Type Description
first_name string Updated first name
last_name string Updated last name
email string Updated email
course string Updated course
is_active boolean Active status
PUT /api/v1/students/f5e6d7c8-1234-5678-abcd-ef1234567890
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
Content-Type: application/json

{
  "course": "Data Science",
  "is_active": false
}

Response (200 OK):

{
  "message": "Student updated",
  "student": {
    "id": "f5e6d7c8-1234-5678-abcd-ef1234567890",
    "first_name": "Alice",
    "last_name": "Smith",
    "email": "alice@university.com",
    "course": "Data Science",
    "enrollment_date": "2026-02-13T10:30:00+00:00",
    "is_active": false
  }
}

Delete Student

Method Endpoint Auth Required
DELETE /students/<id> βœ… Yes
DELETE /api/v1/students/f5e6d7c8-1234-5678-abcd-ef1234567890
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...

Response (200 OK):

{
  "message": "Student deleted"
}

Error Responses

All API errors follow a consistent format:

{
  "error": "Error type",
  "message": "Detailed error description"
}
Status Code Error Type Common Causes
400 Bad Request Missing required fields, invalid JSON
401 Unauthorized Missing/invalid/expired JWT token
403 Forbidden Insufficient permissions
404 Not Found Resource doesn't exist
409 Conflict Duplicate username or email
422 Unprocessable Entity Validation failed
500 Internal Server Error Server-side error (details logged)

πŸ“Š Data Models

User Model

@dataclass
class User:
    id: str              # UUID v4 (auto-generated)
    username: str        # Unique username
    password_hash: str   # PBKDF2-SHA256 hashed password
    role: str = "user"   # User role (default: "user")

JSON Storage Format:

{
  "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "username": "admin",
  "password_hash": "pbkdf2:sha256:600000$...",
  "role": "admin"
}

Student Model

@dataclass
class Student:
    id: str                    # UUID v4 (auto-generated)
    first_name: str            # Student's first name
    last_name: str             # Student's last name
    email: str                 # Unique email address
    course: str                # Enrolled course
    enrollment_date: str       # ISO 8601 timestamp (auto-generated)
    is_active: bool = True     # Active status (default: True)

JSON Storage Format:

{
  "id": "f5e6d7c8-1234-5678-abcd-ef1234567890",
  "first_name": "Alice",
  "last_name": "Smith",
  "email": "alice@university.com",
  "course": "Computer Science",
  "enrollment_date": "2026-02-20T10:30:00+00:00",
  "is_active": true
}

πŸ§ͺ Testing

Automated Tests

The project includes 12+ automated tests covering all endpoints:

# Run all tests
pytest

# Run with verbose output
pytest -v

# Run specific test file
pytest tests/test_students.py -v

# Run with coverage report
pytest --cov=app --cov-report=term-missing

# Generate HTML coverage report
pytest --cov=app --cov-report=html
# Open htmlcov/index.html in browser

Test Coverage Breakdown:

Test File Tests Description
test_health.py 1 Health endpoint verification
test_auth.py 5 Registration, duplicate handling, login, authentication
test_students.py 6 CRUD operations, authorization checks

Test Fixtures:

# tests/conftest.py
@pytest.fixture
def app():
    """Create test application instance."""
    
@pytest.fixture
def client(app):
    """Create test client."""
    
@pytest.fixture
def auth_headers(client):
    """Get authentication headers for protected endpoints."""

Testing with Postman

Step-by-step Workflow:

  1. Register a new user

    • POST http://localhost:5000/api/v1/auth/register
    • Body: {"username": "testuser", "password": "Test123!"}
  2. Login to get tokens

    • POST http://localhost:5000/api/v1/auth/login
    • Body: {"username": "testuser", "password": "Test123!"}
    • Copy the access_token from response
  3. Configure Authorization

    • Go to Authorization tab
    • Select Bearer Token
    • Paste the access token
  4. Test Student Endpoints

    • Now you can access all /students endpoints

Import Postman Collection

Create a Postman collection with these requests:

πŸ“ Student API
β”œβ”€β”€ 🟒 Health Check
β”‚   └── GET {{base_url}}/health
β”œβ”€β”€ πŸ“ Auth
β”‚   β”œβ”€β”€ POST {{base_url}}/auth/register
β”‚   └── POST {{base_url}}/auth/login
└── πŸ“ Students
    β”œβ”€β”€ GET {{base_url}}/students
    β”œβ”€β”€ GET {{base_url}}/students/:id
    β”œβ”€β”€ POST {{base_url}}/students
    β”œβ”€β”€ PUT {{base_url}}/students/:id
    └── DELETE {{base_url}}/students/:id

Quick Test with cURL

# 1. Register a new user
curl -X POST http://localhost:5000/api/v1/auth/register \
  -H "Content-Type: application/json" \
  -d '{"username": "admin", "password": "Admin123!"}'

# 2. Login and save token
TOKEN=$(curl -s -X POST http://localhost:5000/api/v1/auth/login \
  -H "Content-Type: application/json" \
  -d '{"username": "admin", "password": "Admin123!"}' | jq -r '.access_token')

# 3. Create a student
curl -X POST http://localhost:5000/api/v1/students \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $TOKEN" \
  -d '{"first_name": "Alice", "last_name": "Smith", "email": "alice@example.com", "course": "CS"}'

# 4. List all students
curl -H "Authorization: Bearer $TOKEN" \
  http://localhost:5000/api/v1/students

# 5. Get a specific student (replace STUDENT_ID)
curl -H "Authorization: Bearer $TOKEN" \
  http://localhost:5000/api/v1/students/<STUDENT_ID>

# 6. Update a student
curl -X PUT http://localhost:5000/api/v1/students/<STUDENT_ID> \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $TOKEN" \
  -d '{"course": "Data Science"}'

# 7. Delete a student
curl -X DELETE -H "Authorization: Bearer $TOKEN" \
  http://localhost:5000/api/v1/students/<STUDENT_ID>

PowerShell Alternative

# Register
$response = Invoke-RestMethod -Uri "http://localhost:5000/api/v1/auth/register" `
  -Method POST -ContentType "application/json" `
  -Body '{"username": "admin", "password": "Admin123!"}'

# Login
$login = Invoke-RestMethod -Uri "http://localhost:5000/api/v1/auth/login" `
  -Method POST -ContentType "application/json" `
  -Body '{"username": "admin", "password": "Admin123!"}'

$token = $login.access_token

# Create Student
Invoke-RestMethod -Uri "http://localhost:5000/api/v1/students" `
  -Method POST -ContentType "application/json" `
  -Headers @{Authorization = "Bearer $token"} `
  -Body '{"first_name": "Alice", "last_name": "Smith", "email": "alice@example.com", "course": "CS"}'

# List Students
Invoke-RestMethod -Uri "http://localhost:5000/api/v1/students" `
  -Headers @{Authorization = "Bearer $token"}

🐳 Docker Details

Dockerfile Features

Feature Description
Multi-stage build Smaller final image (~150MB)
Non-root user Runs as appuser for security
Gunicorn Production WSGI server with 4 workers
Health check Built-in HEALTHCHECK instruction
Slim base python:3.12-slim for minimal footprint

Dockerfile Breakdown

# Stage 1: Build dependencies
FROM python:3.12-slim AS builder
# Install Python packages to /install prefix

# Stage 2: Production image
FROM python:3.12-slim AS production
# Create non-root user
# Copy only necessary files
# Configure health check
# Run with Gunicorn

Docker Commands Reference

# Build image
docker build -t student-api .

# Build with no cache
docker build --no-cache -t student-api .

# Run container (foreground)
docker run -p 5000:5000 \
  -e SECRET_KEY=my-secret \
  -e JWT_SECRET_KEY=jwt-secret \
  student-api

# Run container (background)
docker run -d -p 5000:5000 \
  -e SECRET_KEY=my-secret \
  -e JWT_SECRET_KEY=jwt-secret \
  --name student-api \
  student-api

# View running containers
docker ps

# View logs
docker logs student-api
docker logs -f student-api  # Follow logs

# Execute command in container
docker exec -it student-api /bin/bash

# Stop container
docker stop student-api

# Remove container
docker rm student-api

# Remove image
docker rmi student-api

Docker Compose Commands

# Start services
docker-compose up

# Start in background
docker-compose up -d

# Rebuild and start
docker-compose up --build

# View logs
docker-compose logs
docker-compose logs -f api  # Follow specific service

# Stop services
docker-compose stop

# Stop and remove
docker-compose down

# Stop, remove, and delete volumes
docker-compose down -v

# View running services
docker-compose ps

πŸ“¦ CI/CD Pipeline (GitHub Actions)

The workflow at .github/workflows/ci.yml runs on every push to main/develop and on pull requests:

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Lint   β”‚ ──▢ β”‚  Test   β”‚ ──▢ β”‚ Build & Zip β”‚ ──▢ β”‚ Docker Build β”‚
β”‚ (flake8)β”‚     β”‚ (pytest)β”‚     β”‚  (artifact) β”‚     β”‚  (verify)    β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                     β”‚                 β”‚                    β”‚
                     β–Ό                 β–Ό                    β–Ό
              πŸ“Š Coverage       πŸ“¦ student-api.zip    🐳 Docker image
                 Report         (artifact download)     (health check)

Pipeline Stages

Stage Tool Purpose Failure Action
Lint flake8 Static code analysis ❌ Block merge
Test pytest Run automated tests ❌ Block merge
Coverage pytest-cov Generate coverage report πŸ“Š Upload artifact
Build zip Create deployable package πŸ“¦ Upload artifact
Docker docker build Verify container builds ❌ Block merge

Triggering Workflows

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main, develop]

πŸ›‘οΈ Security Features

Feature Implementation Details
Password Hashing PBKDF2-SHA256 Werkzeug's generate_password_hash()
JWT Tokens HS256 signing Short-lived access (1h), long-lived refresh (30d)
Protected Endpoints @jwt_required() All student APIs require valid token
Non-root Docker appuser Container runs as unprivileged user
Error Sanitization Custom handlers 500 errors never leak internal details
Input Validation Service layer All inputs validated before processing
Thread Safety Locking JSON repository uses threading locks

Security Best Practices

  1. Always change default secrets in production

    export SECRET_KEY=$(openssl rand -hex 32)
    export JWT_SECRET_KEY=$(openssl rand -hex 32)
  2. Use HTTPS in production (configure reverse proxy)

  3. Implement rate limiting for auth endpoints (future enhancement)

  4. Regular dependency updates

    pip install --upgrade -r requirements.txt

πŸ”Œ Extending to a Database

The project is designed to swap from JSON files to any database with zero changes to API or service code:

Step 1: Create New Repository Implementation

# app/repositories/sqlalchemy_repository.py
from typing import Optional, TypeVar, Type
from sqlalchemy.orm import Session
from app.repositories.base_repository import BaseRepository

T = TypeVar("T")

class SQLAlchemyRepository(BaseRepository[T]):
    """SQLAlchemy-backed repository implementation."""
    
    def __init__(self, session: Session, model_cls: Type[T]) -> None:
        self._session = session
        self._model_cls = model_cls

    def get_all(self) -> list[T]:
        return self._session.query(self._model_cls).all()

    def get_by_id(self, entity_id: str) -> Optional[T]:
        return self._session.query(self._model_cls).get(entity_id)

    def create(self, entity: T) -> T:
        self._session.add(entity)
        self._session.commit()
        return entity

    def update(self, entity_id: str, entity: T) -> Optional[T]:
        existing = self.get_by_id(entity_id)
        if existing:
            # Update fields
            self._session.commit()
        return existing

    def delete(self, entity_id: str) -> bool:
        entity = self.get_by_id(entity_id)
        if entity:
            self._session.delete(entity)
            self._session.commit()
            return True
        return False

Step 2: Update Blueprint to Use New Repository

# app/api/students.py
def _get_service() -> StudentService:
    # Option 1: JSON (current)
    # repo = JsonRepository[Student](filepath, Student)
    
    # Option 2: SQLAlchemy
    # repo = SQLAlchemyRepository[Student](db.session, StudentModel)
    
    return StudentService(repo)

Supported Database Options

Database Package Use Case
PostgreSQL psycopg2-binary, SQLAlchemy Production, relational data
MySQL pymysql, SQLAlchemy Production, relational data
MongoDB pymongo Document storage, flexible schema
SQLite Built-in Development, small deployments
Redis redis Caching, session storage

πŸ”§ Troubleshooting

Common Issues

1. Port Already in Use

Error: Address already in use (port 5000)

Solution:

# Find process using port
netstat -ano | findstr :5000  # Windows
lsof -i :5000                 # macOS/Linux

# Kill process or use different port
python run.py  # Uses PORT env variable

2. JWT Token Expired

{"error": "Token has expired"}

Solution: Login again to get a new access token.

3. Missing Authorization Header

{"error": "Missing Authorization Header"}

Solution: Include the Bearer token:

curl -H "Authorization: Bearer <your_token>" ...

4. Docker Build Fails

Error: pip install failed

Solution:

# Clear Docker cache
docker builder prune
docker build --no-cache -t student-api .

5. Tests Fail with Import Error

ModuleNotFoundError: No module named 'app'

Solution:

# Ensure you're in the project root
cd RestApiGithub

# Install in development mode
pip install -e .
# Or run from project root
python -m pytest

Debug Mode

Enable detailed logging:

export FLASK_ENV=development
export FLASK_DEBUG=1
python run.py

🀝 Contributing

We welcome contributions! Please follow these steps:

1. Fork and Clone

git clone https://github.com/<your-username>/RestApiGithub.git
cd RestApiGithub

2. Create Feature Branch

git checkout -b feature/your-feature-name

3. Make Changes

  • Follow existing code style
  • Add tests for new features
  • Update documentation

4. Run Quality Checks

# Lint
flake8 app tests

# Test
pytest -v

# Coverage
pytest --cov=app --cov-report=term-missing

5. Commit and Push

git add .
git commit -m "feat: add your feature description"
git push origin feature/your-feature-name

6. Create Pull Request

  • Open PR against develop branch
  • Describe your changes
  • Wait for CI checks to pass
  • Request review

Commit Message Convention

<type>(<scope>): <description>

Types:
- feat: New feature
- fix: Bug fix
- docs: Documentation
- style: Formatting
- refactor: Code restructuring
- test: Adding tests
- chore: Maintenance

πŸ“ Data Storage

Data is stored as JSON files in the data/ directory (auto-created at runtime):

File Contents Notes
users.json Registered user accounts Passwords are hashed
students.json Student records Auto-generated enrollment date

The data/ folder is:

  • βœ… Git-ignored (not committed to repository)
  • βœ… Persisted via Docker volume in containerized deployments
  • βœ… Thread-safe with file locking

πŸ“„ License

This project is licensed under the MIT License.

MIT License

Copyright (c) 2026

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

πŸ“ž Support


Made with ❀️ using Flask

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages