DEV Community

Build a Student Management System in Python (CLI + JSON)

Build a Student Management System in Python (CLI + JSON)

A practical, hands-on tutorial to build a terminal-based Student Management System in Python using clean functions, JSON persistence, and real-world patterns you can extend later.

This project is ideal if you’re learning Python fundamentals, prepping for interviews, or assembling portfolio pieces for roles that value scripting, data handling, and CLI tooling. If you’re exploring a Python full stack course in Bangalore with placement, this kind of project fits perfectly into a portfolio that shows practical skills.


What you’ll build

A command-line app that lets you:

  • Add student records (name, roll number, age, course, marks)
  • View all students
  • Search by roll number or name
  • Update marks or course
  • Delete a student
  • Persist data to students.json so records survive restarts

We’ll keep it simple first (one file), then refactor into a small multi-file structure so it feels like a real project.


Who this is for

  • Developers comfortable with basic Python (functions, loops, dicts, files)
  • Anyone who learns best by building small, useful tools
  • People exploring Python for automation, data scripts, or backend foundations

Note: This tutorial avoids heavy frameworks on purpose. You’ll focus on logic, data design, and file I/O—skills that transfer to APIs, ETL jobs, and CLI utilities. This is exactly the kind of work you’d expand on in a Python full stack course in Bangalore with placement, where projects and portfolios matter.


Prerequisites

  • Python 3.10+ installed and on your PATH
  • A code editor (VS Code, PyCharm, or even Sublime)
  • Terminal access (Command Prompt, PowerShell, or macOS/Linux terminal)

Optional but helpful:

  • Git installed (for version control and GitHub examples later)

Project setup

Create a project folder and a virtual environment:

mkdir student_mgmt_cli
cd student_mgmt_cli

# macOS / Linux
python3 -m venv .venv
source .venv/bin/activate

# Windows
python -m venv .venv
.venv\Scripts\activate
Enter fullscreen mode Exit fullscreen mode

Create your main file:

touch student_management.py   # macOS/Linux
# or
echo. > student_management.py  # Windows
Enter fullscreen mode Exit fullscreen mode

Open student_management.py in your editor.


Step 1 — Define the data model

We’ll store each student as a dictionary and keep all students in a list. This keeps things readable and easy to serialize to JSON.

Add this at the top of student_management.py:

import json
from pathlib import Path

DATA_FILE = Path("students.json")
Enter fullscreen mode Exit fullscreen mode

We use pathlib.Path for clean file handling.


Step 2 — Load and save data

Create helper functions to read/write JSON. We’ll handle missing files and basic errors.

def load_students():
    if not DATA_FILE.exists():
        return []
    try:
        with DATA_FILE.open("r", encoding="utf-8") as f:
            return json.load(f)
    except (json.JSONDecodeError, IOError):
        print("⚠️  Data file corrupted. Starting with empty records.")
        return []

def save_students(students):
    try:
        with DATA_FILE.open("w", encoding="utf-8") as f:
            json.dump(students, f, indent=2, ensure_ascii=False)
    except IOError as e:
        print(f"❌ Failed to save data: {e}")
Enter fullscreen mode Exit fullscreen mode

Step 3 — Input validation

Real apps validate input. We’ll write small validators for name, roll number, age, and marks.

def get_non_empty(prompt: str) -> str:
    while True:
        value = input(prompt).strip()
        if value:
            return value
        print("⚠️  This field cannot be empty.")

def get_int(prompt: str, min_val: int | None = None, max_val: int | None = None) -> int:
    while True:
        value = input(prompt).strip()
        try:
            num = int(value)
            if min_val is not None and num < min_val:
                print(f"⚠️  Value must be at least {min_val}.")
                continue
            if max_val is not None and num > max_val:
                print(f"⚠️  Value must be at most {max_val}.")
                continue
            return num
        except ValueError:
            print("⚠️  Please enter a valid integer.")
Enter fullscreen mode Exit fullscreen mode

Step 4 — CRUD functions

We’ll implement Create, Read, Update, Delete operations as separate functions. This keeps the code testable and readable.

Add a student

def add_student(students: list[dict]) -> None:
    print("\n➕ Add Student")

    roll = get_non_empty("Roll number (unique ID): ")
    # Prevent duplicate roll numbers
    if any(s["roll"] == roll for s in students):
        print("❌ Roll number already exists.")
        return

    name = get_non_empty("Full name: ")
    age = get_int("Age: ", min_val=10, max_val=100)
    course = get_non_empty("Course: ")
    marks = get_int("Marks (0–100): ", min_val=0, max_val=100)

    student = {
        "roll": roll,
        "name": name,
        "age": age,
        "course": course,
        "marks": marks,
    }

    students.append(student)
    save_students(students)
    print("✅ Student added.")
Enter fullscreen mode Exit fullscreen mode

View all students

def view_students(students: list[dict]) -> None:
    print("\n📋 All Students")
    if not students:
        print("No records found.")
        return

    for s in students:
        print(
            f"Roll: {s['roll']} | Name: {s['name']} | Age: {s['age']} "
            f"| Course: {s['course']} | Marks: {s['marks']}"
        )
Enter fullscreen mode Exit fullscreen mode

Search by roll or name

def search_student(students: list[dict]) -> None:
    print("\n🔍 Search Student")
    query = input("Enter roll number or part of name: ").strip().lower()
    if not query:
        print("⚠️  Search query cannot be empty.")
        return

    results = [
        s for s in students
        if query in s["roll"].lower() or query in s["name"].lower()
    ]

    if not results:
        print("No matching records.")
        return

    print(f"Found {len(results)} record(s):")
    for s in results:
        print(
            f"Roll: {s['roll']} | Name: {s['name']} | Age: {s['age']} "
            f"| Course: {s['course']} | Marks: {s['marks']}"
        )
Enter fullscreen mode Exit fullscreen mode

Update marks or course

We’ll allow partial updates: only fields the user chooses to change.

def update_student(students: list[dict]) -> None:
    print("\n✏️  Update Student")
    roll = input("Enter roll number to update: ").strip()
    student = next((s for s in students if s["roll"] == roll), None)

    if not student:
        print("❌ Student not found.")
        return

    print(f"Current → Name: {student['name']}, Age: {student['age']}, "
          f"Course: {student['course']}, Marks: {student['marks']}")

    action = input("Update (m)arks or (c)ourse? [m/c]: ").strip().lower()
    if action == "m":
        new_marks = get_int("New marks (0–100): ", min_val=0, max_val=100)
        student["marks"] = new_marks
    elif action == "c":
        new_course = get_non_empty("New course: ")
        student["course"] = new_course
    else:
        print("⚠️  Invalid choice.")
        return

    save_students(students)
    print("✅ Student updated.")
Enter fullscreen mode Exit fullscreen mode

Delete a student

def delete_student(students: list[dict]) -> None:
    print("\n🗑️  Delete Student")
    roll = input("Enter roll number to delete: ").strip()
    student = next((s for s in students if s["roll"] == roll), None)

    if not student:
        print("❌ Student not found.")
        return

    confirm = input(f"Delete {student['name']} (Roll: {roll})? [y/N]: ").strip().lower()
    if confirm != "y":
        print("ℹ️  Deletion cancelled.")
        return

    students.remove(student)
    save_students(students)
    print("✅ Student deleted.")
Enter fullscreen mode Exit fullscreen mode

Step 5 — The menu loop

Wire everything together with a simple text menu.

def menu():
    students = load_students()

    while True:
        print("\n=== Student Management System ===")
        print("1. Add student")
        print("2. View all students")
        print("3. Search student")
        print("4. Update student")
        print("5. Delete student")
        print("6. Exit")

        choice = input("Choose an option (1–6): ").strip()

        if choice == "1":
            add_student(students)
        elif choice == "2":
            view_students(students)
        elif choice == "3":
            search_student(students)
        elif choice == "4":
            update_student(students)
        elif choice == "5":
            delete_student(students)
        elif choice == "6":
            print("👋 Goodbye!")
            break
        else:
            print("⚠️  Invalid option. Try again.")

if __name__ == "__main__":
    menu()
Enter fullscreen mode Exit fullscreen mode

Run it:

python student_management.py
Enter fullscreen mode Exit fullscreen mode

Test the main flows:

  • Add a student with valid data
  • View all students and confirm it appears
  • Search by name and roll
  • Update marks, then view again
  • Delete and verify it’s gone
  • Close and rerun to ensure data persists

Step 6 — Refactor into a small project structure

Single-file scripts are fine for learning, but real projects split concerns. Let’s reorganize into modules similar to common CLI patterns.

Create this structure:

student_mgmt_cli/
├── .venv/
├── data/
│   └── students.json
├── src/
│   ├── __init__.py
│   ├── main.py
│   ├── storage.py
│   ├── validators.py
│   └── operations.py
└── README.md
Enter fullscreen mode Exit fullscreen mode

Move logic:

  • storage.pyload_students, save_students (use data/students.json)
  • validators.pyget_non_empty, get_int
  • operations.pyadd_student, view_students, search_student, update_student, delete_student
  • main.pymenu() and entry point

Example: src/storage.py

import json
from pathlib import Path

DATA_FILE = Path(__file__).resolve().parent.parent / "data" / "students.json"

def load_students():
    if not DATA_FILE.exists():
        DATA_FILE.parent.mkdir(parents=True, exist_ok=True)
        return []
    try:
        with DATA_FILE.open("r", encoding="utf-8") as f:
            return json.load(f)
    except (json.JSONDecodeError, IOError):
        print("⚠️  Data file corrupted. Starting with empty records.")
        return []

def save_students(students):
    try:
        with DATA_FILE.open("w", encoding="utf-8") as f:
            json.dump(students, f, indent=2, ensure_ascii=False)
    except IOError as e:
        print(f"❌ Failed to save data: {e}")
Enter fullscreen mode Exit fullscreen mode

Example: src/main.py

from .storage import load_students
from .operations import (
    add_student,
    view_students,
    search_student,
    update_student,
    delete_student,
)

def menu():
    students = load_students()

    while True:
        print("\n=== Student Management System ===")
        print("1. Add student")
        print("2. View all students")
        print("3. Search student")
        print("4. Update student")
        print("5. Delete student")
        print("6. Exit")

        choice = input("Choose an option (1–6): ").strip()

        if choice == "1":
            add_student(students)
        elif choice == "2":
            view_students(students)
        elif choice == "3":
            search_student(students)
        elif choice == "4":
            update_student(students)
        elif choice == "5":
            delete_student(students)
        elif choice == "6":
            print("👋 Goodbye!")
            break
        else:
            print("⚠️  Invalid option. Try again.")

def run():
    menu()

if __name__ == "__main__":
    run()
Enter fullscreen mode Exit fullscreen mode

Update operations.py to import validators and storage:

from .validators import get_non_empty, get_int
from .storage import save_students
Enter fullscreen mode Exit fullscreen mode

Create a simple runner script at the root:

# run.py
from src.main import run

if __name__ == "__main__":
    run()
Enter fullscreen mode Exit fullscreen mode

Run with:

python run.py
Enter fullscreen mode Exit fullscreen mode

This structure mirrors how small Python CLIs are organized in practice and makes future growth (tests, APIs, packaging) easier. Building projects like this is exactly what you’d do in a Python full stack course in Bangalore with placement, where the focus is on employable, portfolio-ready work.


GitHub example: initialize and push

Version control your project so you can track changes and share it.

git init
git add .
git commit -m "Initial commit: CLI student management system"

# Create a repo on GitHub, then:
git remote add origin https://github.com/your-username/student_mgmt_cli.git
git branch -M main
git push -u origin main
Enter fullscreen mode Exit fullscreen mode

In your README.md, include:

  • Project overview
  • Features list
  • Installation steps
  • Usage examples
  • Sample commands and screenshots (optional)

This becomes a solid portfolio piece for Python roles and full-stack learning paths. If you’re aiming for a Python full stack course in Bangalore with placement, treat this repo as one of your core projects and keep improving it.


Practical exercises

Try these to deepen understanding:

  1. Grade calculation

    Add a function that computes grade (A/B/C/D/F) from marks and displays it alongside each student.

  2. Statistics

    Implement:

    • Average marks across all students
    • Highest and lowest marks
    • Count of students per course
  3. Export to CSV

    Add an option to export the current student list to students.csv using Python’s csv module.

  4. Bulk import

    Allow importing students from a JSON or CSV file (validate and skip duplicates).

  5. Simple tests

    Write a few pytest tests for validators and operations (e.g., duplicate roll detection, invalid marks).

These exercises are great talking points in interviews and fit well into the project work you’d do in a Python full stack course in Bangalore with placement.


Troubleshooting common errors

Error: ModuleNotFoundError: No module named 'src'

  • Ensure you run from the project root: python run.py
  • Confirm src/__init__.py exists (can be empty)

Error: PermissionError when saving JSON

  • Check file/folder permissions
  • On Windows, ensure the data folder isn’t read-only

Data seems lost after restart

  • Verify DATA_FILE path in storage.py
  • Confirm you’re running from the project root so relative paths resolve correctly

Duplicate roll numbers slipping through

  • Re-check the duplicate guard in add_student:
  if any(s["roll"] == roll for s in students):
      print("❌ Roll number already exists.")
      return
Enter fullscreen mode Exit fullscreen mode

Best practices

  • Keep functions small and single-purpose

    Each CRUD operation should do one thing well. This makes debugging and testing easier.

  • Validate early, fail clearly

    Use helpers like get_int and get_non_empty to avoid half-baked records.

  • Separate concerns

    Storage, validation, and operations in different modules keeps the codebase readable and scalable.

  • Handle file errors gracefully

    Wrap file I/O in try/except and provide clear messages instead of raw tracebacks.

  • Use meaningful names

    roll, course, marks are clearer than id1, field2, val3.

Following these practices will make your code look professional—exactly what mentors and recruiters expect when you’re coming from a Python full stack course in Bangalore with placement.


Performance tips (for larger datasets)

This CLI uses a list of dicts, which is fine for hundreds or even a few thousand records. If you scale up:

  • Search optimization Maintain a roll -> student dict for O(1) lookup instead of scanning the list every time.
  students_by_roll = {s["roll"]: s for s in students}
Enter fullscreen mode Exit fullscreen mode
  • Batch saves

    Avoid saving after every tiny change if you’re doing bulk updates; save once at the end.

  • Consider SQLite

    For thousands of records or multi-user scenarios, switch from JSON to SQLite (still pure Python, no server needed).


Common errors and how to avoid them

  • JSONDecodeError on load

    Happens if students.json is manually edited and becomes invalid JSON.

    Fix: Restore from backup or delete the file to start fresh (your code already handles this).

  • Unicode issues with names

    Always open files with encoding="utf-8" and use ensure_ascii=False in json.dump.

  • Accidental data loss on exception

    Don’t overwrite the file if serialization fails. Write to a temp file first, then replace.


Learning resources

  • Python docs: json, pathlib, csv modules
  • Project-based practice: build small CLI tools (todo list, expense tracker, contact book)
  • Next steps:
    • Add a REST API with FastAPI or Flask
    • Build a simple Tkinter GUI for the same logic
    • Integrate SQLite for more robust storage

If you’re in Bengaluru and exploring structured learning, look for a Python full stack course in Bangalore with placement that emphasizes projects, Git, and deployment. Many programs highlight placement support and real-world builds similar to this.


Where to go from here

You now have a working, modular CLI app with:

  • Clean data handling
  • Input validation
  • Persistent storage
  • A structure you can grow

From here, you could:

  • Add authentication (simple PIN or username/password)
  • Build a web frontend (FastAPI + React) that reuses your operations
  • Package it as a CLI tool with click or typer
  • Add logging and basic analytics

This is the kind of end-to-end project that stands out when you’re targeting roles after a Python full stack course in Bangalore with placement. If you want, I can help you extend this into a FastAPI backend or a Tkinter desktop app in a follow-up tutorial.

Top comments (0)