The Palos Publishing Company

Follow Us On The X Platform @PalosPublishing
Categories We Write About

Build a “Today I Learned” tracker

Here’s a complete guide to building a “Today I Learned” (TIL) tracker using a simple web-based application stack. This tracker lets you log daily learnings and reflect on knowledge growth over time.


Overview

Features:

  • Add a new “Today I Learned” entry

  • View all past entries in reverse chronological order

  • Search through entries by keyword

  • Edit or delete entries

Tech Stack:

  • Frontend: HTML, CSS, JavaScript

  • Backend: Node.js with Express

  • Database: SQLite (lightweight and serverless)


1. Project Structure

pgsql
til-tracker/ ├── public/ │ ├── index.html │ ├── styles.css │ └── app.js ├── server.js ├── db.js ├── package.json └── til.db

2. Backend (Node.js + Express)

package.json

json
{ "name": "til-tracker", "version": "1.0.0", "main": "server.js", "scripts": { "start": "node server.js" }, "dependencies": { "express": "^4.18.2", "sqlite3": "^5.1.6", "body-parser": "^1.20.2" } }

server.js

js
const express = require('express'); const bodyParser = require('body-parser'); const db = require('./db'); const app = express(); const PORT = 3000; app.use(bodyParser.json()); app.use(express.static('public')); app.get('/api/entries', async (req, res) => { const rows = await db.all('SELECT * FROM til ORDER BY date DESC'); res.json(rows); }); app.post('/api/entries', async (req, res) => { const { content } = req.body; await db.run('INSERT INTO til (content, date) VALUES (?, datetime("now"))', [content]); res.sendStatus(201); }); app.delete('/api/entries/:id', async (req, res) => { await db.run('DELETE FROM til WHERE id = ?', [req.params.id]); res.sendStatus(204); }); app.put('/api/entries/:id', async (req, res) => { const { content } = req.body; await db.run('UPDATE til SET content = ? WHERE id = ?', [content, req.params.id]); res.sendStatus(200); }); app.listen(PORT, () => { console.log(`TIL tracker running at http://localhost:${PORT}`); });

db.js

js
const sqlite3 = require('sqlite3').verbose(); const { open } = require('sqlite'); async function init() { const db = await open({ filename: './til.db', driver: sqlite3.Database }); await db.run(`CREATE TABLE IF NOT EXISTS til ( id INTEGER PRIMARY KEY AUTOINCREMENT, content TEXT NOT NULL, date TEXT NOT NULL )`); return db; } module.exports = init();

3. Frontend

public/index.html

html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Today I Learned</title> <link rel="stylesheet" href="styles.css"> </head> <body> <div class="container"> <h1>Today I Learned</h1> <form id="entryForm"> <textarea id="entryInput" placeholder="What did you learn today?" required></textarea> <button type="submit">Add Entry</button> </form> <div id="entries"></div> </div> <script src="app.js"></script> </body> </html>

public/styles.css

css
body { font-family: Arial, sans-serif; background: #f9f9f9; margin: 0; padding: 20px; } .container { max-width: 700px; margin: auto; background: white; padding: 20px; border-radius: 8px; } h1 { text-align: center; color: #333; } form { margin-bottom: 20px; } textarea { width: 100%; height: 100px; padding: 10px; font-size: 16px; } button { margin-top: 10px; padding: 10px 20px; font-size: 16px; } .entry { padding: 10px; border-bottom: 1px solid #ddd; } .entry:last-child { border-bottom: none; } .entry small { display: block; color: #666; margin-bottom: 5px; } .entry button { background: none; border: none; color: red; cursor: pointer; font-size: 12px; margin-left: 10px; }

public/app.js

js
document.addEventListener('DOMContentLoaded', () => { const form = document.getElementById('entryForm'); const input = document.getElementById('entryInput'); const entriesDiv = document.getElementById('entries'); async function loadEntries() { const res = await fetch('/api/entries'); const entries = await res.json(); entriesDiv.innerHTML = entries.map(entry => ` <div class="entry" data-id="${entry.id}"> <small>${new Date(entry.date).toLocaleString()}</small> <p>${entry.content}</p> <button onclick="deleteEntry(${entry.id})">Delete</button> </div> `).join(''); } form.addEventListener('submit', async e => { e.preventDefault(); const content = input.value.trim(); if (!content) return; await fetch('/api/entries', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ content }) }); input.value = ''; loadEntries(); }); window.deleteEntry = async function(id) { await fetch(`/api/entries/${id}`, { method: 'DELETE' }); loadEntries(); }; loadEntries(); });

4. Running the App

  1. Install dependencies:

    nginx
    npm install
  2. Run the server:

    sql
    npm start
  3. Open your browser to http://localhost:3000


This TIL tracker is a practical and clean way to document daily learnings. You can expand it with features like tagging, exporting to Markdown, or user accounts. Would you like it to support multiple users or sync with Google Sheets?

Share this Page your favorite way: Click any app below to share.

Enter your email below to join The Palos Publishing Company Email List

We respect your email privacy

Categories We Write About