thecodex.expert · The Codex Family of Knowledge
Tier 2 · Intermediate · Python Project

JSON Note App

Create, list, and delete timestamped notes saved as JSON. Learn to manage a small persistent store of structured records.

🧠 Teaches how to think spoonfed, every age Last verified:

1 The Problem

We want a note-taking app: write a note, it is saved with a timestamp and an ID; list all notes; delete one by ID. Everything persists in a JSON file. It teaches managing a store of structured records with unique IDs and timestamps — the shape of nearly every real app’s data.

Where this shows up: notes apps, messaging, comments, blog posts, audit logs, any feature with “create / list / delete” records that have IDs and timestamps. This is CRUD — the bread and butter of application development.

2 How to Think About It

Think about what each note needs, before any code:

The plan — in plain English
1. Each note has an ID, the text, and a timestamp. → 2. Create assigns the next ID and the current time, then saves. → 3. List shows all notes with their IDs and times. → 4. Delete removes the note with a given ID. → 5. Persist to JSON throughout.

New

List

Delete

Load notes

Show menu

Choice?

Add id + text + timestamp

Show all notes

Remove by id

Save

3 The Build — explained part by part

Here is the complete note app. Each part is explained below.

Pythonnotes.py
import json
import os
from datetime import datetime

FILE = "notes.json"

def load():
    if os.path.exists(FILE):
        with open(FILE) as f:
            return json.load(f)
    return []

def save(notes):
    with open(FILE, "w") as f:
        json.dump(notes, f, indent=2)

def add_note(notes, text):
    # Next id is one more than the highest existing id (or 1 if empty).
    next_id = max((n["id"] for n in notes), default=0) + 1
    notes.append({
        "id": next_id,
        "text": text,
        "created": datetime.now().isoformat(timespec="seconds"),
    })
    return notes

def delete_note(notes, note_id):
    return [n for n in notes if n["id"] != note_id]

if __name__ == "__main__":
    notes = load()
    while True:
        choice = input("\n1. New  2. List  3. Delete  4. Quit: ")
        if choice == "1":
            notes = add_note(notes, input("Note: "))
            save(notes)
        elif choice == "2":
            for n in notes:
                print(f"[{n['id']}] {n['created']}  {n['text']}")
        elif choice == "3":
            notes = delete_note(notes, int(input("Delete id: ")))
            save(notes)
        elif choice == "4":
            break
What each part does — in plain words
from datetime import datetime — gives us the current date and time for timestamps.

max((n["id"] for n in notes), default=0) + 1 — work out the next ID: one more than the biggest existing ID, or 1 if there are none. default=0 handles the empty case so max does not error.

datetime.now().isoformat(timespec="seconds") — the current time as clean text like 2026-06-24T17:30:00. Storing as text means JSON can save it.

add_note builds the record (id + text + created) and appends it.

delete_note — returns a new list with every note except the one whose ID matches. Filtering to remove is cleaner than searching-then-deleting.

json.dump(notes, f, indent=2)indent=2 saves the file nicely formatted, so you can read it by hand.
Common mistakes — and how to avoid them
✗ Using len(notes) + 1 for the next ID — after deletions, this reuses IDs and causes clashes.
✓ Base the next ID on the maximum existing ID, not the count.
✗ Calling max on an empty list — it errors with no default.
✓ Pass default=0 so the first note still works.
✗ Storing a raw datetime object — JSON cannot save it.
✓ Convert to text with .isoformat() first.

4 Test & Prove Each Part

We test creating and deleting notes — the ID logic and the filtering — with known data.

A new note gets ID 1 in an empty store
IDs increase as notes are added
Deleting by ID removes the right note
Pythontest_notes.py
from datetime import datetime


def add_note(notes, text):
    next_id = max((n["id"] for n in notes), default=0) + 1
    notes.append({"id": next_id, "text": text, "created": "now"})
    return notes


def delete_note(notes, note_id):
    return [n for n in notes if n["id"] != note_id]


def test_first_id():
    """The first note gets ID 1."""
    notes = add_note([], "hello")
    assert notes[0]["id"] == 1


def test_increasing_ids():
    """A second note gets ID 2."""
    notes = add_note(add_note([], "a"), "b")
    assert notes[1]["id"] == 2


def test_delete():
    """Deleting ID 1 leaves only the others."""
    notes = add_note(add_note([], "a"), "b")
    remaining = delete_note(notes, 1)
    assert len(remaining) == 1
    assert remaining[0]["id"] == 2

Run with pytest -v. The ID and delete logic live in functions, so we can test them directly. The increasing-IDs test proves new notes never reuse an old ID.

5 The Interface

NEWnote textcreates a timestamped record
What it expects
Note: Buy groceries after work
LISTall notesid, time, text
What it returns
[1] 2026-06-24T17:30:00  Buy groceries after work

6 Run It & Automate It

Run it locally
python3 notes.py
Create notes, list them, delete by ID. Saved in notes.json.

Jenkins runs the tests automatically — each line explained below.

What you should see when it works
Terminala real run
1. New  2. List  3. Delete  4. Quit: 1
Note: Buy groceries

1. New  2. List  3. Delete  4. Quit: 2
[1] 2026-06-24T17:30:00  Buy groceries
If it breaks — how to fix it
🚨 TypeError: Object of type datetime is not JSON serializable.
Store the timestamp as text with .isoformat(), not a datetime object.
🚨 Two notes share an ID after deleting.
You used the count for IDs. Use max(ids) + 1 instead.
GroovyJenkinsfile
// Jenkinsfile — automated tests on every change.
pipeline {
    agent any
    stages {
        stage('Get the code') { steps { checkout scm } }       // download the code
        stage('Install') { steps { sh 'python3 -m venv venv && . venv/bin/activate && pip install pytest' } }  // test tool
        stage('Test') { steps { sh '. venv/bin/activate && pytest -v' } }   // run every test
    }
    post {
        success { echo 'All tests passed.' }
        failure { echo 'A test failed — look above.' }
    }
}
🎯 Try this next — make it yours
  1. Edit a note. Change the text of an existing note by ID. (Teaches: updating records.)
  2. Search. Find notes containing a word. (Teaches: filtering by text.)
  3. Tags. Add tags and list by tag. (Teaches: richer records.)
What you learned
You learned CRUD on structured records — create with unique IDs and timestamps, list, and delete by filtering. This create/list/delete shape underlies almost every application. Related: datetime, json.