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
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.jsonso 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
Create your main file:
touch student_management.py # macOS/Linux
# or
echo. > student_management.py # Windows
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")
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}")
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.")
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.")
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']}"
)
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']}"
)
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.")
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.")
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()
Run it:
python student_management.py
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
Move logic:
-
storage.pyβload_students,save_students(usedata/students.json) -
validators.pyβget_non_empty,get_int -
operations.pyβadd_student,view_students,search_student,update_student,delete_student -
main.pyβmenu()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}")
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()
Update operations.py to import validators and storage:
from .validators import get_non_empty, get_int
from .storage import save_students
Create a simple runner script at the root:
# run.py
from src.main import run
if __name__ == "__main__":
run()
Run with:
python run.py
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
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:
Grade calculation
Add a function that computes grade (A/B/C/D/F) from marks and displays it alongside each student.-
Statistics
Implement:- Average marks across all students
- Highest and lowest marks
- Count of students per course
Export to CSV
Add an option to export the current student list tostudents.csvusing Pythonβscsvmodule.Bulk import
Allow importing students from a JSON or CSV file (validate and skip duplicates).Simple tests
Write a fewpytesttests 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__.pyexists (can be empty)
Error: PermissionError when saving JSON
- Check file/folder permissions
- On Windows, ensure the
datafolder isnβt read-only
Data seems lost after restart
- Verify
DATA_FILEpath instorage.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
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 likeget_intandget_non_emptyto 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 intry/exceptand provide clear messages instead of raw tracebacks.Use meaningful names
roll,course,marksare clearer thanid1,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 -> studentdict for O(1) lookup instead of scanning the list every time.
students_by_roll = {s["roll"]: s for s in students}
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 ifstudents.jsonis 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 withencoding="utf-8"and useensure_ascii=Falseinjson.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,csvmodules - 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
clickortyper - 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.
Originally published by Dev.to AI. Aggregated on AIWithGhost for educational purposes β full credit and traffic to the original publisher.