ABCsteps lesson path

Databases: Store Data Permanently

Use SQLite to understand tables, records, and persistence without adding unnecessary infrastructure. Build one artifact, keep one review trail, and make the work easy to inspect later.

Lesson
14
Time
40 min
Access
public lesson

Learning objective

Understand tables, rows, basic queries, and persistence.

Lab outcome

Store and retrieve leaderboard records with SQLite.

Module milestone

Build a small full-stack leaderboard with persistent data.

Lesson proof workflow

Read, build, then review the evidence.

  1. Node.js hero workflow iconStep 1ReadStart with Persistent storage before touching tools.
  2. JSON hero workflow iconStep 2BuildBuild toward: Store and retrieve leaderboard records with SQLite.
  3. Git hero workflow iconStep 3ReviewReview the evidence using Query basics.

Toolchain

Persistence begins with tables, records, and honest storage boundaries.

These are the practical surfaces used in this lesson. Learn the habit first, then connect it to the wider engineering ecosystem.

Node.js iconNode.jsDatabase access

Connect application code to stored records.

JSON iconJSONData modeling

Shape the records before they enter storage.

Git iconGitMigration discipline

Track schema and query changes as part of the project.

Proof of work

Leave one inspectable trail from this lesson.

The useful output is not a passive note. It is a small artifact another person can inspect: a working file, a command result, a commit, a screenshot, a README note, or a demo link.

Lesson lab: Store and retrieve leaderboard records with SQLite.

Tool and platform logos are ecosystem references only: no affiliation, endorsement, interview access, hiring promise, salary promise, or placement guarantee.

Node.js proof icon

Build

Produce the artifact

Complete the lab and keep the result visible: Store and retrieve leaderboard records with SQLite.

JSON proof icon

Record

Save review evidence

Capture what changed, what broke, and how Persistent storage became clearer through the work.

Git proof icon

Explain

Write the vocabulary

Use your own words for Table modeling and Query basics; this is what makes the lesson inspectable later.

Skills companies recognize

Translate the lesson into inspectable work language.

This lesson turns one small lab into the language a learner can use in a README, demo note, or technical conversation. The point is not to collect logos; the point is to explain work clearly enough that another engineer can inspect it.

Where this skill appears

Data-backed product teams need engineers who know what must persist and what can remain temporary.

Backend teamsData applicationsProduct analytics

Ecosystem references

AWS skill ecosystem logoGoogle Cloud skill ecosystem logoMicrosoft skill ecosystem logoGitHub skill ecosystem logoCloudflare skill ecosystem logoOpenAI skill ecosystem logoGoogle skill ecosystem logo

Platform and company logos are ecosystem references only: no affiliation, endorsement, interview access, hiring preference, salary outcome, or placement guarantee.

Node.js skill proof icon

README line

Name the artifact

Lab proof: Store and retrieve leaderboard records with SQLite. Connect it to Persistent storage so the result reads like work, not a passive note.

JSON skill proof icon

Review line

Explain the stack

Use Node.js, JSON, Git to explain Table modeling and what changed between the first attempt and the inspected result.

Git skill proof icon

Conversation line

Answer with evidence

If a team asks about Query basics, use this proof line: Show the table shape, one insert, one read, and the reason the data survives a refresh.

Proof translation

Node.js proof translation icon

Skill signal

Persistent storage is the market word. The lesson makes it visible through a small working artifact.

JSON proof translation icon

Proof artifact

The inspectable artifact is: Store and retrieve leaderboard records with SQLite.

Git proof translation icon

Interview answer

Use Table modeling and Query basics to explain what changed, what failed, and how you verified it.

Paid guidance

Read publicly. Upgrade when guidance will help you finish.

This lesson remains part of the public written syllabus. Paid help is online-only and human-led: video walkthroughs as they roll out, live class context, WhatsApp Q&A, and project review around the same work.

No account wall, automated checkout, or placement promise is introduced here. Enrollment stays human-led by WhatsApp or call, and the useful proof remains the learner's own artifact.

Node.js paid guidance icon

Public

Written lesson stays open

Read the prepare and review material for lesson 14 on the public site before buying anything.

JSON paid guidance icon

Recorded

Recorded and live guidance clarify the work

Paid guidance can add founder-led video walkthroughs as they roll out and live online class context; the teaching explains the work, but does not replace the written lesson.

Git paid guidance icon

Human

Questions use real context

When stuck, useful guidance starts from the route, error, screenshot, repo fragment, and the lab artifact: Store and retrieve leaderboard records with SQLite.

Phase 1 · Briefing

Lesson briefing

Before You Study (5 mins)

Lesson focus: Lesson 13 made you the API provider — and the leaderboard worked, until you restarted the server and watched every score disappear. Variables in code are ephemeral; they live in memory and die with the process. Databases are the opposite: data written today survives every restart, every deploy, every server move. Today we add SQLite — a battle-tested relational database that lives in a single file with no installation — to your backend. Your API doesn't change shape; the storage layer slots in underneath.

What you should have ready:

  • Lesson 13's backend/ with the in-memory leaderboard API working
  • About 60 minutes
  • A small irrational fear of databases that you'll lose by lunchtime
  • A directory for the database file (./data/) — we'll create it together
  • (Optional) A SQLite GUI: DB Browser for SQLite or VS Code's "SQLite" extension

The Concept

A database is software designed for one job: store data and retrieve it efficiently, even when there's a lot of it, even when many things are reading and writing at once, even when the power blinks. Every other property a database has — query language, indexes, transactions, replication — exists to serve that one job better.

There are two big families of databases, and you'll meet both in your career:

FamilyExamplesWhen to reach for it
Relational (SQL)SQLite, PostgreSQL, MySQL, MariaDBStructured data with relationships (users, orders, products) — ~80% of all real-world apps
Document / NoSQLMongoDB, Firestore, DynamoDB, RedisFlexible schemas, very high write throughput, simple key-value patterns

Today we use SQLite, the simplest possible relational database. It's not a server — there's no sqlite-server process running. It's just a library you import, plus a single file on disk where the data lives. Both Android and iOS use SQLite for their built-in databases. Firefox and Chrome use SQLite for browser history. Most desktop apps that need persistent local storage use SQLite. It is the most-deployed database engine in the world.

The relational model has three key abstractions:

  • Tables — like spreadsheet sheets. A scores table has columns and rows.
  • Columns — the fields each row has. id, name, score, created_at.
  • Rows — individual records. One row = one score entry.

Every column has a type: INTEGER, TEXT, REAL (decimal number), BLOB (binary data), or NULL. Type discipline is one of the things that makes relational databases reliable; the database refuses to store a string in a column declared as INTEGER.

The language you use to talk to a relational database is SQL (Structured Query Language). It's been the dominant query language since the 1970s and isn't going anywhere. Five statements cover ~95% of what you'll do:

sql
CREATE TABLE scores (
  id         INTEGER PRIMARY KEY AUTOINCREMENT,
  name       TEXT    NOT NULL,
  score      INTEGER NOT NULL,
  created_at TEXT    NOT NULL DEFAULT (datetime('now'))
);

INSERT INTO scores (name, score) VALUES ('Divyanshu', 1500);

SELECT * FROM scores WHERE score > 1000 ORDER BY score DESC LIMIT 10;

UPDATE scores SET score = 2000 WHERE id = 1;

DELETE FROM scores WHERE id = 1;

Two more concepts that pay back forever:

  • Primary key — a column declared as PRIMARY KEY uniquely identifies each row. With AUTOINCREMENT, SQLite assigns 1, 2, 3, ... automatically.
  • Indexes — special data structures the database maintains so queries on specific columns stay fast as the table grows. CREATE INDEX idx_score ON scores(score DESC) makes "top 10 scores" stay fast even with a million rows.

The reason relational databases dominated for 50 years is ACID — four properties that guarantee data integrity:

  • Atomic — a transaction either fully succeeds or fully fails; no partial writes
  • Consistent — the database moves from one valid state to another; constraints are never violated
  • Isolated — concurrent transactions don't see each other's intermediate state
  • Durable — once committed, data survives a crash or power loss

For a leaderboard you can probably ignore ACID. For a banking system, ACID is the entire reason the system exists.

The library we'll use today is better-sqlite3 — a fast, synchronous Node.js binding for SQLite that's easier to learn than async drivers and good enough for projects under tens of thousands of writes per second. For very high throughput or web-scale apps, you'd graduate to PostgreSQL with an async driver, but the SQL you learn here transfers verbatim.

Quick Concepts

TermSimple Meaning
DatabaseSoftware for persistent, queryable storage
TableA named collection of rows with the same shape
ColumnOne field of every row in a table — has a type and (often) constraints
RowOne record — like a spreadsheet row
Primary keyA column whose value uniquely identifies the row
SQLThe query language relational databases speak
SQLiteA serverless relational database that lives in a single file
IndexA data structure that makes queries on specific columns fast
TransactionA group of SQL statements that succeed or fail together

What We Will Build

By the end of this lesson, you will have done these specific things:

  1. Installed better-sqlite3npm install better-sqlite3 in your backend project. This is a native module; it might compile briefly during install. That's normal.
  2. Created a data/ directory for the database file. Added data/ to .gitignore so the file doesn't end up in source control.
  3. Initialized the database in a new file backend/db.js:
    javascript
    import Database from 'better-sqlite3'
    const db = new Database('./data/scores.db')
    db.pragma('journal_mode = WAL')          // better concurrency
    
    db.exec(`
      CREATE TABLE IF NOT EXISTS scores (
        id         INTEGER PRIMARY KEY AUTOINCREMENT,
        name       TEXT    NOT NULL,
        score      INTEGER NOT NULL CHECK (score >= 0),
        created_at TEXT    NOT NULL DEFAULT (datetime('now'))
      );
      CREATE INDEX IF NOT EXISTS idx_score ON scores(score DESC);
    `)
    
    export default db
    
  4. Ran the server once to create ./data/scores.db. Verified the file exists with ls -la data/.
  5. Replaced the in-memory array in server.js with prepared SQL statements:
    javascript
    import db from './db.js'
    
    const insertScore = db.prepare('INSERT INTO scores (name, score) VALUES (?, ?)')
    const listScores  = db.prepare('SELECT * FROM scores ORDER BY score DESC LIMIT ?')
    const findScore   = db.prepare('SELECT * FROM scores WHERE id = ?')
    const removeScore = db.prepare('DELETE FROM scores WHERE id = ?')
    
    app.get('/api/scores', (req, res) => {
      const limit = Math.min(Number(req.query.limit) || 10, 100)
      const rows = listScores.all(limit)
      res.json({ scores: rows, count: rows.length })
    })
    
    app.post('/api/scores', (req, res) => {
      const { name, score } = req.body
      // ... validation from Lesson 13 ...
      const result = insertScore.run(name.trim(), score)
      const created = findScore.get(result.lastInsertRowid)
      res.status(201).json(created)
    })
    
    app.delete('/api/scores/:id', (req, res) => {
      const result = removeScore.run(Number(req.params.id))
      if (result.changes === 0) return res.status(404).json({ error: 'Not found' })
      res.status(204).send()
    })
    
  6. Tested with curl — added a few scores, restarted the server, fetched again. The data survived the restart. Felt the difference.
  7. Inspected scores.db with the SQLite CLI:
    bash
    sqlite3 ./data/scores.db
    sqlite> .tables                  -- shows all tables
    sqlite> .schema scores            -- shows the CREATE TABLE statement
    sqlite> SELECT * FROM scores;
    sqlite> .quit
    
  8. (Bonus) Wrote a query that finds the median score (or the score at position 5 if you have 10 entries). The query language is more capable than it looks.

The "felt the difference" in step 6 is the goal. Once you've experienced data surviving a restart, every project from here onward can have real persistence — and you'll start designing systems differently.

Think About

Before studying, consider:

  1. SQL was designed in 1974 by Donald Chamberlin and Raymond Boyce at IBM. It is older than most reading this lesson. Most other 1974 technologies have been replaced. Why has SQL survived? (Hint: a small, declarative vocabulary that maps cleanly to a deeply useful operation — set theory on rows.)
  2. What happens to your scores.db file if your laptop's hard drive fails? (It's gone.) What strategies do real applications use to make sure data survives hardware failure? (Backups, replication, cloud-managed databases. Ideas for Module D and beyond.)

By the End

After this lesson, you'll:

  • ✅ Have better-sqlite3 installed and a scores.db file on disk
  • ✅ Have a db.js module that initializes the schema with IF NOT EXISTS
  • ✅ Have replaced the in-memory array in your API with real SQL queries
  • ✅ Be able to write the five core SQL statements (CREATE, INSERT, SELECT, UPDATE, DELETE)
  • ✅ Have inspected the database from the sqlite3 CLI
  • ✅ Have felt the difference — data surviving a server restart
  • ✅ Be ready for Lesson 15 — the closing milestone for Module C

Memory is short. Disks are long. 💾

Next lesson · 15

Milestone: Online Leaderboard Works

Connect frontend, API, and database, then verify that leaderboard data survives beyond a page refresh.

Vue.js next lesson iconNode.js next lesson iconCloudflare next lesson icon