Build a REST API with Bun and Elysia

Build a smart food suggestion API using Bun, Elysia, and SQLite.

Introduction

30 Second Summary

Every day, the same question haunts lunch hour: what should I eat today? It gets worse when you accidentally end up at the same place three days in a row.

In this project, you will build a backend API that solves this problem by suggesting a restaurant you haven't visited recently. You'll use Bun, Elysia, and SQLite to create a smart food randomizer with full history tracking.

What You'll Build

Hit a single endpoint, get back a restaurant suggestion you haven't eaten at in days, confirm your choice, and watch it get logged so tomorrow's pick stays fresh.

By the end of this project, you'll have:

  • A working random endpoint that filters out recently visited restaurants and picks one you haven't been to in at least 3 days.
  • A confirm and history flow where you lock in your pick, it saves to the database, and you can pull up a list of everything you've eaten recently.
  • A full menu management API you can test with curl requests right from your terminal to add new restaurants on the fly.
  • Secret Mission: Add edit and delete endpoints for full CRUD control over your restaurant list.

Are there any prerequisites?

You should have basic TypeScript or JavaScript knowledge and an understanding of HTTP methods like GET and POST. No prior experience with Bun or Elysia is needed.

Before We Start

Before We Start...

Before we dive in, let's lock in what you're about to build and why it matters.

You're creating a backend API that solves the daily "what should I eat?" dilemma. Instead of a random picker that might suggest the same place you ate yesterday, your API will be smart enough to avoid recent picks.

Install Bun and Scaffold the Elysia Project

To build your restaurant recommendation API, you need two things: the Bun runtime to execute TypeScript at high speed, and an Elysia project skeleton to give your server structure.

In this step, you will install Bun, scaffold a fresh Elysia project, and start the development server to confirm everything works.

In this step, get ready to:
  • Install the Bun runtime.
  • Scaffold an Elysia project with bun create.
  • Start the development server and confirm it responds.
Install Bun

Bun is a fast JavaScript and TypeScript runtime that replaces Node.js for this project. It also includes a built-in SQLite driver you will use later to store restaurant data.

  • Open a terminal. Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
  • Type Terminal (macOS) or PowerShell (Windows) and press Enter to open it.
  • Check if Bun is already installed by running:
bun --version

✔️ I see a version number

Bun is already installed. You are ready to move on to scaffolding the project.

ⓧ Command not found

Bun is not installed yet. Install it by running the command for your operating system.

macOS

  • Install Bun by running:
curl -fsSL https://bun.sh/install | bash
  • Close and reopen your terminal so the new bun command is available.

Windows

  • Install Bun by running:
powershell -c "irm bun.sh/install.ps1|iex"
What does this command do?

This downloads and runs the official Bun installer script from bun.sh. It places the bun executable on your system and adds it to your PATH.

  • Close and reopen PowerShell so the new bun command is available.
  • After reopening your terminal, verify the installation by running:
bun --version

You should see a version number printed. This confirms Bun is installed and ready.

Still seeing command not found?

Make sure you fully closed and reopened your terminal after installing. If the issue persists, your PATH may not have updated. Try opening a brand new terminal window.

On Windows, ensure you are using PowerShell, not Command Prompt (cmd). Bun requires Windows 10 version 1809 or later.

Help me troubleshoot my Bun installation

Create the Elysia project

Elysia is a TypeScript web framework optimized for Bun. The scaffold command creates a project folder with the server entry point and configuration files you need.

  • Move to your Desktop by running this command:
cd ~/Desktop
  • Create the Elysia project by running:
bun create elysia kinrai-api

What does this command do?

This downloads the official Elysia starter template and creates a folder called kinrai-api on your Desktop. Inside you will find src/index.ts (your server entry point), package.json (project dependencies and scripts), and tsconfig.json (TypeScript configuration).

  • Move into the new project folder by running:
cd kinrai-api
  • Confirm the project files exist by running:
ls src

You should see index.ts listed. This is the file where your server code lives.

  • Open the project in VS Code by running:
code .
  • In the VS Code Explorer sidebar, click package.json to open it.
  • Replace the entire contents of package.json with the following:
{
  "name": "kinrai-api",
  "version": "1.0.0",
  "scripts": {
    "dev": "bun --watch src/index.ts",
    "start": "bun src/index.ts"
  },
  "dependencies": {
    "elysia": "^1.4.29"
  }
}

What does this file do?

  • The dev script runs your server with --watch, which auto-reloads whenever you change a file.
  • The start script runs the server without watching. This is for when you are done developing.
  • The elysia dependency at version ^1.4.29 is the web framework your API is built on.
  • Save package.json.
  • Now open tsconfig.json in the Explorer sidebar and replace its entire contents with:
{
  "compilerOptions": {
    "target": "ES2021",
    "module": "ESNext",
    "moduleResolution": "bundler",
    "types": ["bun-types"],
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true
  }
}

What does this configure?

This tells TypeScript to use modern ES module syntax and include Bun's built-in type definitions. The bun-types entry gives you autocomplete for Bun-specific APIs like bun:sqlite.

  • Save tsconfig.json.
  • Install the project dependencies by running this in your VS Code terminal:
bun install

You should see output confirming that elysia was installed. A node_modules folder now exists in your project.

Install failed or timed out?

Make sure your terminal is inside the kinrai-api folder. Run pwd (macOS) or cd (Windows) to check your current directory.

If you see a network error, check your internet connection and try running bun install again.

Help me fix my bun install error

Start the development server

Everything is installed. Time to see your server respond to a real request.

  • Start the Elysia development server by running:
bun dev

What does bun dev do?

This runs src/index.ts with the --watch flag. Whenever you edit and save a file, Bun automatically restarts the server. Your terminal stays occupied while the server runs.

Before you check the browser, what do you expect to see when you visit http://localhost:3000?

  • Open your browser and navigate to http://localhost:3000.

You should see the text Hello Elysia displayed on the page. Your server is running and responding to requests.

Don't see Hello Elysia?

Make sure the bun dev command is still running in your terminal. If you see an error there, read the message carefully. A common cause is another process already using port 3000.

Check that you are visiting http://localhost:3000 (not https).

Help me figure out why my Elysia server is not responding

✔️ Awesome, I've got everything!

Great. Make sure you have saved both package.json and tsconfig.json.

ⓧ I'd like to double check the full code

Here is what each configuration file should look like at this point.

package.json:

{
  "name": "kinrai-api",
  "version": "1.0.0",
  "scripts": {
    "dev": "bun --watch src/index.ts",
    "start": "bun src/index.ts"
  },
  "dependencies": {
    "elysia": "^1.4.29"
  }
}

tsconfig.json:

{
  "compilerOptions": {
    "target": "ES2021",
    "module": "ESNext",
    "moduleResolution": "bundler",
    "types": ["bun-types"],
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true
  }
}

Your environment is ready and your server is alive. Next up, you will create the SQLite database and seed it with restaurant data so the random endpoint has something to pick from.

Create the Database and Seed Menu Data

Your Elysia server is up and greeting visitors at localhost:3000. But an API without data behind it is just an empty shell.

Your API needs a database to store restaurants and track eating history. In this step, you'll use bun:sqlite (built directly into Bun) to create a SQLite database and fill it with Thai restaurant data.

In this step, get ready to:
  • Create a database module with menu and history tables.
  • Seed the database with Thai restaurant data using a transaction.
  • Verify the database initializes correctly by running the script.
Create the database module

Bun ships with a built-in SQLite driver called bun:sqlite. No extra packages to install. You just import it and start creating tables.

  • Stop the dev server by pressing Ctrl+C in your terminal.
  • Create a new file called src/db.ts by running:
touch src/db.ts
  • Open src/db.ts in VS Code and paste the following code:
import { Database } from "bun:sqlite";

// Create (or open) the SQLite database file
const db = new Database("food.db");

// Create the menu table to store restaurant options
db.run(`
  CREATE TABLE IF NOT EXISTS menu (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    name TEXT NOT NULL,
    category TEXT NOT NULL
  )
`);

// Create the history table to track what you've eaten
db.run(`
  CREATE TABLE IF NOT EXISTS history (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    menu_id INTEGER NOT NULL,
    eaten_at TEXT NOT NULL DEFAULT (date('now')),
    FOREIGN KEY (menu_id) REFERENCES menu(id)
  )
`);

What does this code do?

  • new Database("food.db") creates a SQLite file called food.db in your project root. If it already exists, it opens it instead.
  • The menu table holds restaurant names and categories (rice, noodle, somtam, etc.).
  • The history table records which restaurant you ate at and when, using a foreign key back to menu.
  • CREATE TABLE IF NOT EXISTS means this script is safe to run multiple times without errors.
  • Save src/db.ts.
  • Run the file to create the database:
bun run src/db.ts
  • Confirm food.db was created by running:
ls food.db

You should see food.db listed in the output. The tables are ready, but they're empty.

Not seeing food.db?

  • Make sure you saved src/db.ts before running it.
  • Check that your terminal is inside the kinrai-api folder. Run pwd to confirm.
  • help me debug why food.db isn't being created
Seed sample data

Empty tables don't help your random endpoint pick a restaurant. You need starter data so the API has something to work with from day one.

  • In src/db.ts, add the following code below the second db.run(...) block (after the history table creation):
// Seed the database with sample restaurants if empty
function seed() {
  const count = db.query("SELECT COUNT(*) as count FROM menu").get() as { count: number };
  if (count.count > 0) return;

  const insert = db.prepare("INSERT INTO menu (name, category) VALUES (?, ?)");
  const seedData = db.transaction((items: [string, string][]) => {
    for (const [name, category] of items) {
      insert.run(name, category);
    }
  });

  seedData([
    ["ข้าวมันไก่", "rice"],
    ["ข้าวกะเพราหมูกรอบ", "rice"],
    ["ก๋วยเตี๋ยวเรือ", "noodle"],
    ["บะหมี่แห้ง", "noodle"],
    ["ส้มตำไทย", "somtam"],
    ["ข้าวซอย", "noodle"],
    ["ผัดไทย", "noodle"],
    ["ข้าวหมูแดง", "rice"],
    ["ราดหน้า", "noodle"],
    ["ต้มยำกุ้ง", "soup"],
  ]);

  console.log("Database initialized with 10 restaurants");
}

seed();

export { db };

What does this code do?

  • The seed() function first checks if the menu table already has data. If it does, it exits early to avoid duplicates.
  • db.prepare() compiles the INSERT statement once. db.transaction() wraps all 10 inserts into a single atomic operation. All succeed together or none do.
  • The 10 seed items cover different categories: rice dishes, noodles, somtam, and soup.
  • export { db } makes the database connection available to your route handlers later.

Why use a transaction for seeding?

Without a transaction, each INSERT is its own disk write. With 10 items, that's 10 separate writes.

A transaction batches them into a single write. This is faster and guarantees that either all restaurants get inserted or none do.

  • Save src/db.ts.
Run the database script standalone

Time to verify everything works end-to-end. The seed function should detect the empty tables and fill them with your 10 restaurants.

  • First, delete the existing empty database so the seed runs fresh:
rm food.db

Before you run the script, what do you think you'll see printed in the terminal?

  • Run the database script:
bun run src/db.ts

You should see this output:

Database initialized with 10 restaurants

Your database is created, the tables exist, and 10 Thai restaurants are seeded and ready.

  • Run the script again to confirm the duplicate-prevention logic works:
bun run src/db.ts

This time there's no output. The seed() function detected existing data and exited early.

Still seeing an error?

  • If you see a syntax error, check that the seedData([...]) array uses square brackets around each pair and that every line ends with a comma.
  • If you see a type error about count, make sure the as { count: number } type assertion is present on the .get() call.
  • help me fix my database seed script error

✔️ Awesome, I've got everything!

Great. Double check you've saved src/db.ts and that food.db exists in your project folder.

ⓧ I'd like to double check the full code

Your src/db.ts should contain these sections in order. Scroll up and confirm each one is present:

  • The import { Database } from "bun:sqlite" line and new Database("food.db") creation.
  • Two db.run(...) blocks creating the menu and history tables.
  • The seed() function with the count check, transaction, and 10 restaurant entries.
  • The seed() call and export { db } at the bottom.

Your database layer is complete. Next up, you'll build the core API endpoints that query this data and pick a random restaurant for you.

Build the Random and Confirm Endpoints

Your database has 10 restaurants ready to go. Now you need the core logic that picks one at random.

A truly random picker would suggest the same place you ate yesterday. Your GET /api/random endpoint will be smarter. It queries the database, excludes anything eaten in the last 3 days, then picks from what remains.

In this step, get ready to:
  • Build a GET endpoint that randomly suggests a restaurant you haven't eaten at recently.
  • Build a POST endpoint that confirms your food choice and logs it to history.
  • Test both endpoints with curl to verify they work together.
Create GET /api/random

The random endpoint is the brain of your app. It uses a SQL query with a LEFT JOIN to check each menu item's eating history, then filters out anything eaten within 3 days.

  • Replace the entire contents of src/index.ts with the following code:
import { Elysia, t } from "elysia";
import { db } from "./db";

const app = new Elysia()
  .get("/api/random", () => {
    // Query menu items not eaten in the last 3 days, pick one at random
    const items = db.query(`
      SELECT m.id, m.name, m.category,
        CAST(julianday('now') - julianday(MAX(h.eaten_at)) AS INTEGER) as days_since
      FROM menu m
      LEFT JOIN history h ON m.id = h.menu_id
      GROUP BY m.id
      HAVING days_since IS NULL OR days_since >= 3
      ORDER BY RANDOM()
      LIMIT 1
    `).get() as { id: number; name: string; category: string; days_since: number | null } | null;
    // Fallback: pick randomly if everything was eaten recently
    if (!items) {
      const fallback = db.query("SELECT id, name, category FROM menu ORDER BY RANDOM() LIMIT 1").get() as { id: number; name: string; category: string } | null;
      if (!fallback) return { error: "No menu items found" };
      return { ...fallback, daysSinceLastEaten: 0, note: "All items eaten recently, picking randomly" };
    }
    return {
      id: items.id,
      name: items.name,
      category: items.category,
      daysSinceLastEaten: items.days_since ?? "never",
    };
  })
  .listen(3000);
console.log(`Server running at http://localhost:${app.server!.port}`);

What does this code do?

The SQL query is the key piece. It joins menu with history using a LEFT JOIN so menu items with no history still appear.

  • julianday('now') - julianday(MAX(h.eaten_at)) calculates how many days since you last ate each item.
  • HAVING days_since IS NULL OR days_since >= 3 keeps only items never eaten or eaten 3+ days ago.
  • ORDER BY RANDOM() LIMIT 1 picks one random result from the filtered set.
  • The fallback handles the edge case where every restaurant was eaten recently. Instead of returning nothing, it picks any random item.
  • Save src/index.ts.

Because bun dev is running with --watch, the server restarts automatically when you save.

  • Test the random endpoint by running this in a new terminal:
curl http://localhost:3000/api/random

You should see a JSON response with a restaurant name, category, and daysSinceLastEaten set to "never" (since you haven't confirmed eating anywhere yet).

Seeing an error or empty response?

  • If you see a connection error, make sure bun dev is still running in your other terminal.
  • If you get a syntax error in the terminal running bun dev, double check that your backtick template literal for the SQL query is closed properly.
  • Make sure food.db exists in your project root. If it was deleted, run bun run src/db.ts again to recreate it.

Why is my random endpoint not returning data?

Create POST /api/confirm

When you pick a restaurant, you need a way to tell the API you actually ate there. The confirm endpoint records your choice in the history table so future random picks can avoid it.

  • In src/index.ts, find this line:
  })
  .listen(3000);
  • Add the POST route between the closing }) of the GET route and .listen(3000), so it looks like this:
  })
  .post("/api/confirm", ({ body }) => {
    const { menuId } = body;
    // Check that the menu item exists before recording
    const item = db.query("SELECT id FROM menu WHERE id = ?").get(menuId) as { id: number } | null;
    if (!item) return { error: "Menu item not found" };
    // Record the meal in history with today's date
    db.run("INSERT INTO history (menu_id, eaten_at) VALUES (?, date('now'))", [menuId]);
    return { success: true, message: "Recorded!" };
  }, {
    body: t.Object({
      menuId: t.Number()
    })
  })
  .listen(3000);

What does this code do?

  • The handler destructures menuId from the request body.
  • It validates the menu item exists before inserting. This prevents orphan history records.
  • The third argument to .post() is a schema definition using t.Object() from Elysia. This gives you runtime validation and TypeScript type safety on the request body.
  • Save src/index.ts.
  • Test the confirm endpoint by running:
curl -X POST http://localhost:3000/api/confirm -H "Content-Type: application/json" -d "{\"menuId\": 1}"

You should see {"success":true,"message":"Recorded!"} in your terminal.

Getting a validation error?

  • Make sure you include the -H "Content-Type: application/json" header. Without it, Elysia cannot parse the JSON body.
  • Check that menuId is a number, not a string. The schema validation with t.Number() rejects string values.

Why is my POST request failing with a validation error?

Test the full flow with curl

Now that both endpoints are wired up, you can test the complete flow. First get a suggestion, then confirm it, then check that the random endpoint adjusts.

  • Before you run the next command, what do you think will happen if you call /api/random multiple times? Will you always get the same restaurant or different ones?
  • Run the random endpoint a few times by running:
curl http://localhost:3000/api/random
  • Run it 2-3 more times.

You should see different restaurant suggestions each time thanks to ORDER BY RANDOM() in the SQL query.

  • Now confirm a choice and then test random again by running:
curl -X POST http://localhost:3000/api/confirm -H "Content-Type: application/json" -d "{\"menuId\": 1}"

You should see {"success":true,"message":"Recorded!"} confirming menu item 1 is now in your history.

Why does the same restaurant still appear after confirming?

The exclusion logic filters items eaten within the last 3 days. Since you just confirmed item 1 today, it will be excluded from results for the next 3 days. If you confirm all 10 items, the fallback kicks in and picks randomly anyway.

✔️ Awesome, I've got everything!

Great. Double check that you have saved src/index.ts and that bun dev shows no errors in the terminal.

ⓧ I'd like to double check the full code

Your src/index.ts should contain these sections, in order:

  • Two import lines: Elysia and t from "elysia", and db from "./db".
  • A const app = new Elysia() declaration starting the chain.
  • The .get("/api/random", ...) route handler with the SQL query, fallback logic, and return statement.
  • The .post("/api/confirm", ...) route handler with body validation using t.Object({ menuId: t.Number() }).
  • The .listen(3000) call chained at the end.
  • A console.log line printing the server URL.

Scroll up and confirm each code block from substep 3.1 and 3.2 is present and matches.

Your API now has a brain. It picks restaurants intelligently and records your choices. Next up, you will build the history and menu management endpoints to complete the full feature set.

Build the History and Menu Endpoints

Your API can suggest a random restaurant and record your choice. But there's no way to look back at what you've eaten, or add new restaurants to the list.

In this step, you'll add three routes that complete the data layer. A history endpoint shows your recent meals, and two menu endpoints let you browse and expand your restaurant list.

In this step, get ready to:
  • Create GET /api/history to view recent meals.
  • Create GET /api/menu and POST /api/menu to browse and add restaurants.
  • Run a full integration test through all endpoints.
Create GET /api/history

The history endpoint joins the history table with menu so you get human-readable restaurant names alongside dates. Without the join, you'd only see numeric IDs.

  • In src/index.ts, find the line .listen(3000); at the bottom of the file.
  • Add the following route above .listen(3000), directly after the closing of the .post("/api/confirm", ...) route:
  .get("/api/history", () => {
    // Join history with menu to get restaurant names
    const rows = db.query(`
      SELECT h.id, m.name, m.category, h.eaten_at
      FROM history h
      JOIN menu m ON h.menu_id = m.id
      ORDER BY h.eaten_at DESC, h.id DESC
      LIMIT 30
    `).all() as { id: number; name: string; category: string; eaten_at: string }[];
    return rows;
  })

What does this code do?

The SQL JOIN connects each history entry to its matching menu item, so the response includes the restaurant name instead of just a numeric menu_id.

Ordering by eaten_at DESC, h.id DESC puts the most recent meals first. The LIMIT 30 keeps responses snappy as your history grows.

  • Save src/index.ts. The dev server auto-restarts thanks to --watch.
  • Test the history endpoint by running this in a separate terminal:
curl http://localhost:3000/api/history

You should see a JSON array. If you confirmed a meal in the previous step, it appears here with the restaurant name and date. If you haven't confirmed anything yet, you'll see an empty array [].

Seeing a syntax error or empty response?

  • Check that the new .get("/api/history", ...) is chained after the previous route's closing }) and before .listen(3000). Missing the dot at the start breaks the chain.
  • Confirm the terminal running bun dev didn't show any red error text after you saved.

Help me debug my /api/history endpoint

Create GET /api/menu and POST /api/menu

GET /api/menu lets you see all available restaurants. POST /api/menu lets you add new ones without touching the database file directly.

  • In src/index.ts, add these two routes directly after the .get("/api/history", ...) route you just wrote, still above .listen(3000):
  .get("/api/menu", () => {
    // Return all restaurants ordered by id
    const rows = db.query("SELECT id, name, category FROM menu ORDER BY id").all() as { id: number; name: string; category: string }[];
    return rows;
  })
  .post("/api/menu", ({ body }) => {
    // Insert a new restaurant into the menu
    const { name, category } = body;
    const result = db.run("INSERT INTO menu (name, category) VALUES (?, ?)", [name, category]);
    return { success: true, id: result.lastInsertRowid };
  }, {
    body: t.Object({
      name: t.String(),
      category: t.String()
    })
  })

What does this code do?

GET /api/menu queries every row from the menu table and returns them as a JSON array. This gives the frontend (or you, via curl) a way to see all options.

POST /api/menu uses t.Object() schema validation to ensure both name and category are strings before inserting. Elysia rejects invalid requests automatically with a 422 status.

  • Save src/index.ts.
  • Test adding a new restaurant by running:
curl -X POST http://localhost:3000/api/menu -H "Content-Type: application/json" -d "{\"name\": \"ราดหน้า\", \"category\": \"noodle\"}"

You should see a response like {"success":true,"id":11}. The id number may differ depending on how many items already exist.

  • Confirm it was saved by running:
curl http://localhost:3000/api/menu

You should see your full restaurant list as a JSON array, with the new item at the end.

POST returning a validation error?

  • Make sure you included -H "Content-Type: application/json" in the curl command. Without it, Elysia cannot parse the JSON body.
  • Verify your JSON is valid. Both name and category must be strings wrapped in quotes.

Help me fix my POST /api/menu request

Run the full integration test

Time to test the entire flow end-to-end. You'll add a restaurant, get a random suggestion, confirm it, and verify it shows up in history.

  • Add a brand new restaurant to test with:
curl -X POST http://localhost:3000/api/menu -H "Content-Type: application/json" -d "{\"name\": \"ไก่ทอดหาดใหญ่\", \"category\": \"rice\"}"
  • Get a random suggestion:
curl http://localhost:3000/api/random

Note the id in the response. You'll use it to confirm.

  • Before you confirm, what do you think will happen to the history endpoint after this? Confirm the suggestion by running this command (replace 1 with the id from the random response):
curl -X POST http://localhost:3000/api/confirm -H "Content-Type: application/json" -d "{\"menuId\": 1}"
  • Check your history to see the confirmed meal:
curl http://localhost:3000/api/history

You should see a JSON array with your just-confirmed meal at the top, including the restaurant name and today's date.

What did you just prove?

The full cycle works. A new restaurant can be added, the random picker can suggest it, confirming records it to history, and the history endpoint surfaces it with a human-readable name and date. All four endpoints talk to the same SQLite database correctly.

History shows an empty array?

  • The confirm step must succeed first. Check that the response from POST /api/confirm says {"success":true,"message":"Recorded!"}.
  • If you see {"error":"Menu item not found"}, the menuId you used doesn't exist. Run curl http://localhost:3000/api/menu to see valid IDs.

Help me debug my integration test flow

✔️ Awesome, I've got everything!

Great. Double check you've saved src/index.ts and that bun dev is still running without errors.

ⓧ I'd like to double check the full code

Your src/index.ts should now contain these route handlers chained together, in order:

  • .get("/api/random", ...) with the exclusion query and fallback logic.
  • .post("/api/confirm", ...) that inserts into the history table.
  • .get("/api/history", ...) with the JOIN query, ordered by most recent first, limited to 30.
  • .get("/api/menu", ...) returning all items ordered by id.
  • .post("/api/menu", ...) with t.Object validation for name and category.
  • .listen(3000) at the very end of the chain.

Scroll up through this step and confirm each chunk is present in your file, in that order. The src/db.ts and package.json files remain unchanged from the previous step.

Your API now has a complete data layer. You can browse restaurants, add new ones, pick a random suggestion, confirm your choice, and review your eating history. In the Secret Mission, you'll add the ability to edit and delete restaurants for full CRUD control.

Secret mission

Add Edit and Delete Endpoints

Your restaurant list can grow, but what happens when a place closes down or you misspell a name? Right now there's no way to fix or remove entries. Time to add PUT and DELETE endpoints for full CRUD control.

Clean Up Your Resources

Clean Up Your Resources

This project runs entirely locally, so there are no ongoing costs. Decide whether to keep your resources running, pause them to come back later, or delete them entirely.

Resources you used:

  • The kinrai-api project folder on your Desktop (contains source code and database).
  • The food.db SQLite database file inside your project folder.
  • The bun dev development server process (running in your terminal).
  • Bun runtime installed on your system.

Keep everything running

No action needed. Choose this if you are still actively building on this project or plan to extend it soon.

  • Your dev server stays running in the terminal. You can stop it anytime with Ctrl+C and restart with bun dev.
  • Bun stays installed on your machine for future projects.
  • Your food.db file retains all your test data.

Pause - I'll come back to this later

Stop the running process to free up your terminal, but keep all files in place so you can pick up where you left off.

  • In the terminal where bun dev is running, press Ctrl+C to stop the server.
  • Your project files and database remain on your Desktop. To resume later, open a terminal in the kinrai-api folder and run bun dev again.

Delete - I don't want to use this again

Remove all project files and optionally uninstall Bun from your system.

  • In the terminal where bun dev is running, press Ctrl+C to stop the server.
  • Delete the project folder by running these commands:

macOS

cd ~/Desktop
rm -rf kinrai-api

Windows

cd ~\Desktop
rmdir /s /q kinrai-api
  • Uninstall Bun from your system:

macOS

rm -rf ~/.bun

Windows

powershell -c ~\.bun\uninstall.ps1

Nice Work!

Nice Work!

Nice work! You just built a complete backend API that solves the daily "what should I eat?" problem with smart randomization logic.

You've learned how to:

  • Build a complete REST API with Elysia running on Bun, handling GET and POST routes with schema validation.
  • Create and manage a SQLite database using bun:sqlite with tables, seed data, prepared statements, and transactions.
  • Implement smart randomization logic that excludes recently eaten restaurants using SQL date arithmetic.
  • (Secret Mission) Add PUT and DELETE endpoints with proper 404 handling and cascade deletion for a full CRUD API.

Ready to quiz yourself?