Build a SQLite CRUD API with Deno: A Step-by-Step Guide

80 views

Creating a CRUD (Create, Read, Update, Delete) application using SQLite in a Deno environment is a great way to understand both SQLite as a database and Deno as a runtime environment. Here's a step-by-step guide to help you build this application.

Prerequisites:

  • You should have Deno installed on your machine.
  • Basic understanding of JavaScript/TypeScript.

Step 1: Setup the Project

  1. Initialize Deno Project: Create a directory for your project.

    mkdir deno-sqlite-crud
    cd deno-sqlite-crud
    
  2. Create Files: Create the main file app.ts, and other necessary files such as db.ts for handling database connections and queries.

Step 2: Setup Database

  1. Install SQLite Driver: Deno requires third-party permissions for file access and network access, for security purposes. While there is no need to 'install' modules like Node.js, you import them directly. For SQLite, we'll use a third-party module.

    Create import_map.json to manage dependencies cleanly:

    {
      "imports": {
        "sqlite": "https://deno.land/x/sqlite/mod.ts"
      }
    }
    

    Then run:

    deno run --unstable --import-map=import_map.json --allow-read --allow-write app.ts
    
  2. Setup Database Connection in db.ts:

    import { DB } from "sqlite";
    
    const db = new DB("tasks.db");
    
    db.query(`
      CREATE TABLE IF NOT EXISTS tasks (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        title TEXT,
        completed BOOLEAN
      )
    `);
    
    export default db;
    

Step 3: CRUD Operations

  1. Create CRUD operations in db.ts:

    // Create
    export const createTask = (title: string) => {
      db.query("INSERT INTO tasks (title, completed) VALUES (?, ?)", [title, false]);
    };
    
    // Read
    export const getTasks = () => {
      return [...db.query("SELECT id, title, completed FROM tasks")].map(([id, title, completed]) => ({
        id,
        title,
        completed,
      }));
    };
    
    // Update
    export const updateTask = (id: number, completed: boolean) => {
      db.query("UPDATE tasks SET completed = ? WHERE id = ?", [completed, id]);
    };
    
    // Delete
    export const deleteTask = (id: number) => {
      db.query("DELETE FROM tasks WHERE id = ?", [id]);
    };
    

Step 4: Setup a Basic API

  1. Create a basic server in app.ts:

    import { serve } from "https://deno.land/std@0.106.0/http/server.ts";
    import { createTask, getTasks, updateTask, deleteTask } from "./db.ts";
    
    const server = serve({ port: 8000 });
    console.log("Server running on http://localhost:8000/");
    
    for await (const req of server) {
      const url = new URL(req.url, `http://localhost`);
      let bodyContent = "";
    
      if (req.method === "GET" && url.pathname === "/tasks") {
        const tasks = getTasks();
        bodyContent = JSON.stringify(tasks);
      } 
      else if (req.method === "POST" && url.pathname === "/tasks") {
        const decoder = new TextDecoder();
        const body = decoder.decode(await Deno.readAll(req.body));
        const { title } = JSON.parse(body);
        createTask(title);
        req.respond({ status: 201, body: JSON.stringify({ message: "Task created" }) });
        continue;
      } 
      else if (req.method === "PUT" && url.pathname.startsWith("/tasks/")) {
        const id = parseInt(url.pathname.split("/")[2]);
        const decoder = new TextDecoder();
        const body = decoder.decode(await Deno.readAll(req.body));
        const { completed } = JSON.parse(body);
        updateTask(id, completed);
        req.respond({ status: 200, body: JSON.stringify({ message: "Task updated" }) });
        continue;
      } 
      else if (req.method === "DELETE" && url.pathname.startsWith("/tasks/")) {
        const id = parseInt(url.pathname.split("/")[2]);
        deleteTask(id);
        req.respond({ status: 200, body: JSON.stringify({ message: "Task deleted" }) });
        continue;
      } 
      else {
        req.respond({ status: 404, body: "Not Found" });
        continue;
      }
    
      req.respond({ status: 200, body: bodyContent });
    }
    

Step 5: Running Your Application

Run the application by enabling read and write access for the database file.

deno run --allow-net --allow-read --allow-write --import-map=import_map.json app.ts

Notes:

  • Security: The above setup is suitable for local development. For production, tightening security permissions and handling CORS properly is crucial.
  • Robustness: Add error handling and more sophisticated logic to parse inputs correctly, handle edge cases, and avoid SQL injections.
  • Cleaning Up: Ensure to properly close the database connection if needed when shutting down the server.

This simple CRUD application using Deno and SQLite should give you a foundational understanding of connecting these technologies and building a simple API. You can further expand it with more features, authentication, or even integrate with a front-end framework.