Proyek Mini Python: Aplikasi To-Do di Terminal
Saatnya merangkai semua dasar yang sudah kamu pelajari menjadi satu program utuh yang benar-benar bisa kamu pakai setiap hari.
Oleh Afa Hernandes12 menit baca
Daftar isi
- Sebelum mulai
- Langkah 1: Rancang datanya dulu
- Langkah 2: Menu yang terus berulang
- Langkah 3: Menambah dan melihat tugas
- Langkah 4: Menandai selesai dan menghapus, dengan input yang aman
- Langkah 5: Simpan ke file JSON supaya tidak hilang
- Hasil
- Kode lengkap
- Error yang sering muncul
- Latihan
- Langkah berikutnya: rilis dan amankan
- Pertanyaan yang sering diajukan
- Kenapa menyimpan ke JSON, bukan ke database?
- Apakah aplikasi ini bisa dipakai di HP atau dibuka orang lain?
- Programku jalan, tapi kodenya berbeda dari tutorial ini. Apakah salah?
- Kesimpulan
Kamu sudah belajar variabel, percabangan dan perulangan, function, serta list dan dictionary. Masing-masing terasa masuk akal saat dipelajari sendiri-sendiri. Tantangan berikutnya adalah merangkainya menjadi satu program yang utuh, dan itu keterampilan tersendiri.
Di proyek mini ini kamu akan membuat aplikasi to-do di terminal. Programnya menampilkan menu, bisa menambah tugas, menandai tugas selesai, menghapus tugas, dan mengingat daftarnya walaupun program ditutup, karena datanya disimpan ke file. Ukurannya kecil (sekitar 100 baris), tapi bentuknya sama dengan aplikasi sungguhan: ada data, ada tampilan, ada aturan, dan ada penyimpanan.
Bangun pelan-pelan, langkah demi langkah, dan jalankan programnya setiap kali satu langkah selesai.
Sebelum mulai
- Python 3 sudah terpasang dan kamu bisa menjalankan file dari terminal. Kalau belum, ikuti menyiapkan VS Code dan terminal.
- Buat folder baru, misalnya
todo, lalu buka di VS Code. Semua kode di artikel ini ditulis di satu file:todo.py. - Semua kode diuji dengan Python 3.13 di Windows 11. Tidak ada library tambahan:
jsondanpathlibsudah bawaan Python.
Langkah 1: Rancang datanya dulu
Sebelum menulis kode, tentukan bentuk datanya. Satu tugas punya dua keterangan: judulnya dan statusnya (sudah selesai atau belum). Itu cocok dengan dictionary. Banyak tugas yang berurutan cocok dengan list. Jadi seluruh data aplikasi ini adalah list berisi dictionary:
tugas = [ {"judul": "Beli kopi", "selesai": True}, {"judul": "Kerjakan tugas Python", "selesai": False},]Kebiasaan merancang data lebih dulu ini penting. Kalau bentuk datanya sudah jelas, setiap fitur tinggal menjawab pertanyaan sederhana: “fitur ini mengubah list-nya bagaimana?” Menambah tugas berarti append, menghapus berarti pop, menandai selesai berarti mengubah "selesai" menjadi True.
Langkah 2: Menu yang terus berulang
Aplikasi terminal biasanya berupa perulangan: tampilkan menu, baca pilihan, kerjakan, lalu ulangi sampai pengguna memilih keluar. Mulai dari kerangkanya:
def main(): tugas = [] while True: print() print("=== To-Do ===") print("1. Lihat tugas") print("2. Tambah tugas") print("3. Tandai selesai") print("4. Hapus tugas") print("5. Keluar") pilihan = input("Pilih (1-5): ").strip()
if pilihan == "5": print("Sampai jumpa!") break else: print("Pilihan tidak dikenal. Ketik angka 1 sampai 5.")
if __name__ == "__main__": main()Jalankan dengan python todo.py. Menu muncul, dan hanya angka 5 yang bekerja. Itu memang disengaja: kerangkanya jalan dulu, fiturnya menyusul.
while Truemengulang tanpa henti, danbreakadalah satu-satunya jalan keluar..strip()membuang spasi di awal dan akhir input, jadi" 5 "tetap dianggap"5".- Pilihan dibandingkan sebagai teks (
"5", bukan5), karenainput()selalu menghasilkan teks. if __name__ == "__main__":artinya “jalankanmain()hanya kalau file ini dijalankan langsung”. Untuk sekarang, anggap saja sebagai pintu masuk program.
Langkah 3: Menambah dan melihat tugas
Setiap fitur dibuat sebagai function sendiri yang menerima list tugas. Tambahkan dua function ini di atas def main():
def tampilkan(tugas): if not tugas: print("Belum ada tugas. Tambahkan dengan menu 2.") return for nomor, t in enumerate(tugas, start=1): tanda = "x" if t["selesai"] else " " print(f"{nomor}. [{tanda}] {t['judul']}") sisa = len([t for t in tugas if not t["selesai"]]) print(f"{sisa} dari {len(tugas)} tugas belum selesai.")
def tambah(tugas): judul = input("Judul tugas: ").strip() if not judul: print("Judul tidak boleh kosong.") return tugas.append({"judul": judul, "selesai": False}) print(f"Ditambahkan: {judul}")Lalu sambungkan ke menu, di dalam main() sebelum if pilihan == "5":
if pilihan == "1": tampilkan(tugas) elif pilihan == "2": tambah(tugas) elif pilihan == "5":Perhatikan bahwa if pilihan == "5": yang lama sekarang menjadi elif. Beberapa hal baru di sini:
if not tugas:bernilai benar kalau list-nya kosong. List kosong dianggap “salah” oleh Python.enumerate(tugas, start=1)memberi nomor urut mulai dari 1, karena pengguna terbiasa menghitung dari 1, bukan dari 0."x" if t["selesai"] else " "adalahifversi satu baris: hasilnya"x"kalau tugas selesai, dan spasi kalau belum.[t for t in tugas if not t["selesai"]]disebut list comprehension: membuat list baru berisi tugas yang belum selesai. Bacanya: “ambil setiaptditugasyang belum selesai”.returndi tengah function langsung menghentikan function itu. Pola “cek dulu, keluar kalau tidak valid” membuat kode utamanya tidak bersarang terlalu dalam.
Kenapa tambah() bisa mengubah list di main() tanpa return? Karena list yang dikirim ke function adalah list yang sama, bukan salinannya. append di dalam function mengubah list yang juga dipegang main().
Langkah 4: Menandai selesai dan menghapus, dengan input yang aman
Dua fitur ini sama-sama meminta nomor tugas, dan di situlah pengguna paling sering salah ketik: mengetik huruf, angka 0, atau nomor yang tidak ada. Daripada menulis pemeriksaan yang sama dua kali, buat satu function pembantu:
def pilih_nomor(tugas, pesan): """Minta nomor tugas. Kembalikan indeksnya di list, atau None kalau tidak valid.""" teks = input(pesan).strip() try: nomor = int(teks) except ValueError: print(f"'{teks}' bukan angka.") return None if not 1 <= nomor <= len(tugas): print(f"Tidak ada tugas nomor {nomor}.") return None return nomor - 1
def tandai_selesai(tugas): i = pilih_nomor(tugas, "Nomor tugas yang selesai: ") if i is None: return tugas[i]["selesai"] = True print(f"Selesai: {tugas[i]['judul']}")
def hapus(tugas): i = pilih_nomor(tugas, "Nomor tugas yang dihapus: ") if i is None: return terhapus = tugas.pop(i) print(f"Dihapus: {terhapus['judul']}")int(teks)mengubah teks menjadi angka. Kalau teksnya bukan angka (misalnya"dua"), Python melemparValueError.trydanexceptmenangkap error itu, sehingga program menampilkan pesan yang ramah alih-alih berhenti mendadak.1 <= nomor <= len(tugas)memeriksa apakah nomornya ada dalam rentang. Python membolehkan perbandingan berantai seperti ini.- Pengguna mengetik nomor mulai dari 1, sedangkan indeks list mulai dari 0. Karena itu function ini mengembalikan
nomor - 1. Noneberarti “tidak ada hasil”. Pemanggilnya cukup memeriksaif i is None: return.
Sambungkan ke menu:
elif pilihan == "3": tandai_selesai(tugas) elif pilihan == "4": hapus(tugas)Coba semua menu sekarang. Semuanya bekerja, tapi tutup programnya lalu jalankan lagi: daftarnya hilang. Data di dalam variabel hanya hidup selama program berjalan.
Langkah 5: Simpan ke file JSON supaya tidak hilang
Supaya data bertahan, simpan ke file. Formatnya JSON, format teks yang bentuknya hampir sama dengan list dan dictionary Python. JSON juga format yang dipakai hampir semua aplikasi web untuk bertukar data, jadi ini bekal yang berguna.
Tambahkan di paling atas file:
import jsonfrom pathlib import Path
FILE_DATA = Path(__file__).with_name("todo.json")
def muat_tugas(): """Baca daftar tugas dari file. Kalau file belum ada, mulai dari list kosong.""" if not FILE_DATA.exists(): return [] with FILE_DATA.open(encoding="utf-8") as f: return json.load(f)
def simpan_tugas(tugas): """Tulis seluruh daftar tugas ke file.""" with FILE_DATA.open("w", encoding="utf-8") as f: json.dump(tugas, f, ensure_ascii=False, indent=2)Path(__file__).with_name("todo.json")artinya “filetodo.jsondi folder yang sama dengantodo.py”. Dengan begitu file datanya selalu ketemu, dari folder mana pun kamu menjalankan programnya.with ... as f:membuka file dan menutupnya otomatis setelah blok selesai, bahkan kalau terjadi error.json.loadmembaca file menjadi list Python.json.dumpmenulis list menjadi teks JSON.indent=2membuat filenya rapi dibaca manusia, danensure_ascii=Falsemembuat huruf seperti “é” tetap tertulis apa adanya.encoding="utf-8"penting di Windows, supaya karakter non-Inggris tidak berubah menjadi simbol aneh.
Lalu pakai kedua function itu. Di main(), ganti tugas = [] dengan:
tugas = muat_tugas()Dan panggil simpan_tugas(tugas) setiap kali data berubah, yaitu di tambah(), tandai_selesai(), dan hapus(), tepat sebelum baris print(...) terakhirnya. Contohnya di tambah():
tugas.append({"judul": judul, "selesai": False}) simpan_tugas(tugas) print(f"Ditambahkan: {judul}")Menyimpan langsung setiap kali ada perubahan lebih aman daripada menyimpan saat keluar saja: kalau terminal tertutup tiba-tiba, datamu tidak ikut hilang.
Hasil
Jalankan python todo.py, tambah dua tugas, tandai satu selesai, lalu lihat daftarnya. Ini sesi yang benar-benar dijalankan (menu yang berulang dipotong supaya ringkas):
=== To-Do ===1. Lihat tugas2. Tambah tugas3. Tandai selesai4. Hapus tugas5. KeluarPilih (1-5): 2Judul tugas: Beli kopiDitambahkan: Beli kopi
Pilih (1-5): 2Judul tugas: Kerjakan tugas PythonDitambahkan: Kerjakan tugas Python
Pilih (1-5): 3Nomor tugas yang selesai: 1Selesai: Beli kopi
Pilih (1-5): 11. [x] Beli kopi2. [ ] Kerjakan tugas Python1 dari 2 tugas belum selesai.
Pilih (1-5): 4Nomor tugas yang dihapus: dua'dua' bukan angka.
Pilih (1-5): 4Nomor tugas yang dihapus: 7Tidak ada tugas nomor 7.
Pilih (1-5): 5Sampai jumpa!Sekarang di folder proyek ada file todo.json. Buka di VS Code, isinya bisa dibaca manusia:
[ { "judul": "Beli kopi", "selesai": true }, { "judul": "Kerjakan tugas Python", "selesai": false }]Perhatikan true dan false ditulis huruf kecil. Itu cara JSON menulis nilai boolean, dan json.load mengubahnya kembali menjadi True dan False milik Python. Jalankan programnya lagi dan pilih menu 1: tugasmu masih ada.
Kode lengkap
import jsonfrom pathlib import Path
FILE_DATA = Path(__file__).with_name("todo.json")
def muat_tugas(): """Baca daftar tugas dari file. Kalau file belum ada, mulai dari list kosong.""" if not FILE_DATA.exists(): return [] with FILE_DATA.open(encoding="utf-8") as f: return json.load(f)
def simpan_tugas(tugas): """Tulis seluruh daftar tugas ke file.""" with FILE_DATA.open("w", encoding="utf-8") as f: json.dump(tugas, f, ensure_ascii=False, indent=2)
def tampilkan(tugas): if not tugas: print("Belum ada tugas. Tambahkan dengan menu 2.") return for nomor, t in enumerate(tugas, start=1): tanda = "x" if t["selesai"] else " " print(f"{nomor}. [{tanda}] {t['judul']}") sisa = len([t for t in tugas if not t["selesai"]]) print(f"{sisa} dari {len(tugas)} tugas belum selesai.")
def tambah(tugas): judul = input("Judul tugas: ").strip() if not judul: print("Judul tidak boleh kosong.") return tugas.append({"judul": judul, "selesai": False}) simpan_tugas(tugas) print(f"Ditambahkan: {judul}")
def pilih_nomor(tugas, pesan): """Minta nomor tugas. Kembalikan indeksnya di list, atau None kalau tidak valid.""" teks = input(pesan).strip() try: nomor = int(teks) except ValueError: print(f"'{teks}' bukan angka.") return None if not 1 <= nomor <= len(tugas): print(f"Tidak ada tugas nomor {nomor}.") return None return nomor - 1
def tandai_selesai(tugas): i = pilih_nomor(tugas, "Nomor tugas yang selesai: ") if i is None: return tugas[i]["selesai"] = True simpan_tugas(tugas) print(f"Selesai: {tugas[i]['judul']}")
def hapus(tugas): i = pilih_nomor(tugas, "Nomor tugas yang dihapus: ") if i is None: return terhapus = tugas.pop(i) simpan_tugas(tugas) print(f"Dihapus: {terhapus['judul']}")
def main(): tugas = muat_tugas() while True: print() print("=== To-Do ===") print("1. Lihat tugas") print("2. Tambah tugas") print("3. Tandai selesai") print("4. Hapus tugas") print("5. Keluar") pilihan = input("Pilih (1-5): ").strip()
if pilihan == "1": tampilkan(tugas) elif pilihan == "2": tambah(tugas) elif pilihan == "3": tandai_selesai(tugas) elif pilihan == "4": hapus(tugas) elif pilihan == "5": print("Sampai jumpa!") break else: print("Pilihan tidak dikenal. Ketik angka 1 sampai 5.")
if __name__ == "__main__": main()Error yang sering muncul
json.decoder.JSONDecodeError: Illegal trailing comma before end of array: line 2 column 35 (char 36)
File todo.json rusak, biasanya karena diedit manual dan ada koma setelah item terakhir. JSON lebih ketat daripada Python: koma di akhir tidak boleh. Pesan error lain dari keluarga yang sama, misalnya Expecting value, juga berarti isi filenya bukan JSON yang valid. Perbaiki baris yang disebut di pesan error, atau hapus todo.json kalau isinya tidak penting (programnya akan mulai dari daftar kosong).
KeyError: 'Judul'
Nama kunci dictionary salah ketik. Python membedakan huruf besar dan kecil, jadi "Judul" dan "judul" adalah kunci yang berbeda. Samakan dengan nama kunci di data.
TypeError: can only concatenate str (not "int") to str
Hasil input() selalu teks. Kalau kamu menghitung dengannya tanpa int() lebih dulu, Python menolak menjumlahkan teks dengan angka. Di program ini, pengubahan itu ditangani pilih_nomor().
FileNotFoundError: [Errno 2] No such file or directory: 'todo.json'
Muncul kalau kamu membuka file dengan open("todo.json") biasa lalu menjalankan program dari folder lain. Nama file tanpa folder dicari di folder tempat terminal berada, bukan di folder todo.py. Karena itu tutorial ini memakai Path(__file__).with_name(...) dan memeriksa FILE_DATA.exists() dulu.
Tekan Ctrl+C kalau programmu terjebak dalam perulangan tanpa akhir. Panduan membaca pesan error secara lengkap ada di membaca pesan error Python.
Latihan
Coba kerjakan sendiri dulu sebelum bertanya ke AI. Kalau mentok, minta petunjuk, bukan jawaban jadi (lihat belajar ngoding dengan AI tanpa jadi bergantung).
- Tambahkan menu “Ubah judul tugas” yang memakai
pilih_nomor()lagi. - Buat menu 3 bisa membatalkan status selesai: kalau tugasnya sudah selesai, kembalikan menjadi belum selesai (petunjuk:
not). - Tambahkan menu “Hapus semua yang sudah selesai” (petunjuk: list comprehension seperti di
tampilkan()). - Tampilkan tugas yang belum selesai lebih dulu (petunjuk:
sorted()dengankey=).
Langkah berikutnya: rilis dan amankan
Aplikasi terminal tidak bisa di-deploy ke internet seperti situs web, tapi kodenya bisa disimpan dan dibagikan lewat GitHub. Ikuti Git dan GitHub untuk kerja tim, lalu perhatikan satu hal: todo.json berisi datamu sendiri, bukan kode. Masukkan ke .gitignore supaya tidak ikut terunggah:
todo.jsonBiasakan sejak proyek kecil: kode masuk repository, data pribadi dan kunci rahasia tidak. Alasannya dibahas tuntas di environment variable dan secrets.
Satu lagi kebiasaan aman yang sudah kamu terapkan di proyek ini: jangan percaya input pengguna. pilih_nomor() memeriksa setiap input sebelum dipakai. Di aplikasi web, input yang tidak diperiksa adalah pintu masuk serangan seperti SQL injection.
Pertanyaan yang sering diajukan
Kenapa menyimpan ke JSON, bukan ke database?
Untuk satu pengguna dan puluhan tugas, file JSON sudah cukup dan tidak perlu memasang apa pun. Database seperti SQLite mulai berguna saat datanya banyak, perlu dicari dengan cepat, atau diakses beberapa program sekaligus. Perbandingannya ada di MySQL vs SQLite.
Apakah aplikasi ini bisa dipakai di HP atau dibuka orang lain?
Belum. Aplikasi terminal hanya jalan di komputer yang menjalankan Python. Untuk bisa dibuka siapa pun lewat browser, aplikasinya perlu dibuat sebagai halaman web, dan itu yang akan kamu lakukan di artikel berikutnya.
Programku jalan, tapi kodenya berbeda dari tutorial ini. Apakah salah?
Belum tentu. Ada banyak cara benar untuk menulis program yang sama. Ukurannya: apakah semua menu bekerja, apakah input yang salah ditangani dengan baik, dan apakah kamu masih paham kodenya kalau membacanya lagi seminggu kemudian.
Kesimpulan
Proyek kecil ini memakai hampir semua dasar yang sudah kamu pelajari: list berisi dictionary untuk data, perulangan untuk menu, function untuk setiap fitur, try/except untuk input yang salah, dan file JSON supaya data tidak hilang. Pola “rancang data, buat kerangka, tambah fitur satu per satu” juga berlaku untuk aplikasi yang jauh lebih besar.
Langkah berikutnya di jalur Mulai Ngoding dari Nol: membuat aplikasi to-do yang sama sebagai halaman web pertamamu dengan HTML, CSS, dan JavaScript, supaya bisa dibuka di browser dan dibagikan ke orang lain.



