Using SQLite with Next.js

picture

2023-09-21

Using SQLite with Next.js

什麼是嵌入式資料庫

不需另外安裝,就能和系統程式語言整合到應用程式中的輕量級資料庫。由於資料庫就儲存在應用程式的本地文件 中,無須網路連線,資料的存取可以更快速。嵌入式資料庫通常不需要複雜的配置過程,應用程式在運行時即可自 動創建和管理資料庫。沒有額外的伺服器,所以部署和安裝也相對簡單,通常只須將資料庫文件和應用程式一起打 包即可。而且大多數嵌入式資料庫都支持標準的 SQL 查詢語言,使得資料操作更便利,特別適合使用手機應用程 式、桌面應用程式、嵌入式系統和遊戲開發等。

常見的嵌入式資料庫

  1. SQLite:輕量級、零配置、支持標準 SQL 查詢語言和跨平台(包括移動設備和桌面應用程式),符合 ACID (Atomicity, Consistency, Isolation, Durability)。但是 SQLite 在多個使用者同時寫入操作時可能會受到 一些限制,因此不適用於高流量的場景。SQLite 提供了最簡單和高效的本地資料儲存解決方案,如果應用程 式為單用戶取向,未來也不會需要支援多用戶訪問,那麼 SQLite 會是最好的嵌入式資料庫選擇。
  2. Firebird:輕量級、支持跨平台和 ACID ,確保資料一致性。與 SQLite 不同的是,Firebird 能支援多個使用 者同時訪問,特別適用於多用戶、伺服器端的場景。另外,Firebird 是完全開源的,可以自由使用和修改。
  3. H2 Database:使用 Java 開發的輕量級嵌入式資料庫,支援 SQL 和 JDBC(Java Database Connectivity,是 Java 語言中用來規範客戶端程式如何來訪問資料庫的應用程式介面)。可以在本地快速啟動,並支援瀏覽器的 Console 介面。但由於其主要針對 Java,所以不太適用於非 Java 的應用程式。
  4. Realm:適用於移動應用程式,特別是需要離線儲存和同步功能的場景,支援 iOS 和 Android,並提供跨平台 的同步功能。Realm 還提供物件映射(ORM)API,避免讓使用者直接接觸 SQL 語法,簡化了程式語言的複雜度 。但缺點是其關聯式查詢需求可能受限,相對不如其他嵌入式資料庫靈活。

SQLite 效能檢測

  • 測試內容如下,每項測試 5 次取平均值:

    1. Insert:插入多筆 trade 資料,內容為 idpriceamounttimestamp
    2. Read:從 1000 筆/1 萬筆/10 萬筆/100 萬筆 的資料表中讀取任一筆 trade 10 萬次
  • 根據實驗結果,得到以下結論:

    1. SQLite 的 Insert TPS 約為 每秒 527.76 次
    2. SQLite 的 Read 10 萬筆 row 的 TPS 約為 每秒 31000 次

實驗結果

Insert

  • Insert 1000 筆
第一次 第二次 第三次 第四次 第五次 平均值
2.185s 2.314s 2.187s 2.585s 2.151s 2.284s
  • Insert 1 萬筆
第一次 第二次 第三次 第四次 第五次 平均值
16.684s 15.433s 15.617s 20.116s 17.027s 17.375s
  • Insert 10 萬筆
第一次 第二次 第三次 第四次 第五次 平均值
2:45.127 2:46.373 2:56.607 2:47.190 2:55.462 2:50.552(m:ss.mmm)

Read

  • Read 1000 筆資料表
第一次 第二次 第三次 第四次 第五次 平均值
30.104s 29.709s 30.221s 29.890s 34.809s 30.947s
  • Read 1 萬筆資料表
第一次 第二次 第三次 第四次 第五次 平均值
31.299s 33.073s 31.359s 30.215s 33.944s 31.978s
  • Read 10 萬筆資料表
第一次 第二次 第三次 第四次 第五次 平均值
30.401s 30.367s 30.385s 30.812s 32.621s 30.917s
  • Read 100 萬筆資料表
第一次 第二次 第三次 第四次 第五次 平均值
34.572s 35.603s 34.607s 29.824s 39.734s 34.868s

連接 SQLite

以下是提供在 Next.js 中連接 SQLite 資料庫的工具:

  1. Prisma:一套可以透過 JavaScript 或 TypeScript 來操作資料庫套件,幫助開發人員更輕鬆地管理資料庫。 能夠串接 PostgreSQLMySQLSQLiteMongoDB 等資料庫,並且提供 GUI 介面。Prisma 可以自動 從資料庫結構生成模型,而無需手動定義模型;還提供資料庫遷移功能,模型結構變化時也能輕鬆地同步資料 庫結構,不必手動更新資料庫。相較於其他工具,Prisma 擁有許多自動化功能,使開發者更高效地操作和管 理資料庫
  2. TypeORM:一個支援多種資料庫的 ORM 框架,其中就包括 SQLite,能夠使用 TypeScript 定義模型並進行查詢 。
  3. Sequelize:另一個流行的 Node.js ORM,提供了強大的查詢和模型定義功能,支援多種包括 SQLite 的資料庫 。
  4. SQLite3 NPM Package:如果想以更原生的方式進行 SQLite 連接,可以直接使用 sqlite3 NPM 套件。該套件 提供一個能夠使用 SQL 語句直接與 SQLite 進行互動的簡單介面。

安裝 Prisma

  • 透過 npm 安裝 Prisma
npm i -D prisma
  • 安裝 Prisma cilent
npm i @prisma/client

設置 schema.prisma

  • 初始化 Prisma
npx prisma init
  • 完成後可以看到專案裡自動建立好的 prisma/schema.prisma,這個文件將定義資料庫的結構和模型,並通過 Prisma CLI 來生成相關的程式碼和工具
image
  • 如果使用的是 VS Code ,可以安裝 Prisma 套件讓文件自動標色,提升可讀性

    image
  • 另外,在 .env 也會出現預設的 database url ,將 DATABASE_URL 改為 file:./dev.db

image

撰寫 Prisma Schema

設定 Data Source

datasource db {
  provider = "sqlite"
  url      = env("DATABASE_URL")
}
  • 這裡定義資料庫種類是 SQLite ,並指定 URL 為 .env 中的 DATABASE_URL 參數

設定 Data Modal

model Post {
  id          String   @id
  title       String
  description String
  categories  String
  picture     String
  author      String
  content     String
  date        DateTime @default(now())
}
  • 這裡定義資料表的欄位、屬性和關聯。以下為標籤的說明
    • @id:主鍵
    • @unique:表示欄位不能重覆
    • @default:設定欄位預設值,例如 @default(autoincrement()) 自動遞增、@default(now()) 預設為 目前時間等...
    • @relation:描述不同 model 的欄位關聯性,詳細的說明可參 考官方文件

生成 Migration

npx prisma migrate dev --name init
  • migration.sql 中記錄了要放入資料庫的 Table 。在本例中只有一個 Post Tableimage

接下來執行 Prisma Studio:

npx prisma studio
  • 執行後就可以在瀏覽器上透過 GUI 操作剛才生成的 Table

image

連接 Next.js

import {PrismaClient} from '@prisma/client';
const prisma = new PrismaClient();

完成資料庫的設定後,就可以在 Next.js 中進行 CRUD

Create

const saveContact = await prisma.post.create({
  data: {
    id: 'km-20230818001',
    title: 'I am a title',
    description: "I'm a description",
    categories: 'category',
    picture: '/picture_src',
    author: 'julian',
    content: 'I am content',
  },
});

Read

// Filter by a single value
const post = await prisma.post.findMany({
  where: {
    id: {
      endsWith: '001',
    },
  },
});

// Get all
const allPosts = await prisma.post.findMany();

Update

const updatePost = await prisma.post.update({
  where: {
    id: 'km-20230818001',
  },
  data: {
    categories: 'newbie',
  },
});

Delete

const deletePosts = await prisma.post.deleteMany({
  where: {
    author: {
      contains: 'julian',
    },
  },
});

應用實例

  • API 內容
// /api/addPosts.ts
import {NextApiRequest, NextApiResponse} from 'next';
import {PrismaClient} from '@prisma/client';

const prisma = new PrismaClient();

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
  // Info (20230818 - Julian) Check if the method is POST
  if (req.method !== 'POST') {
    return res.status(405).json({message: 'Method not allowed'});
  }

  const contactData = JSON.parse(req.body);
  // Info (20230818 - Julian) Create a new contact in the database
  const saveContact = await prisma.post.create({
    data: contactData,
  });

  res.status(200).json(saveContact);
}
  • Form component (節錄)
const FormComponent  = () => {
  {...}
  // Info: (20230818 - Julian) Call API to add new post
  const addPost = async (kmData: IKMData) => {
    const response = await fetch('/api/addPost', {
      method: 'POST',
      body: JSON.stringify(kmData),
    });

    if (!response.ok) {
      throw new Error(response.statusText);
    }

    return await response.json();
  };

  // Info: (20230818 - Julian) Click submit button
  const submitHandler = async (event: React.FormEvent<HTMLFormElement>) => {
    const newData: IKMData = {
      id: kmId,
      author: inputAuthorId,
      title: inputTitle,
      description: inputDescription,
      picture: inputPicture,
      categories: inputCategory,
      content: inputContent,
    };

    try {
      event.preventDefault();

      await addPost(newData);
      ...
    } catch (error) {
      console.log(error);
    }
  };

  return(...)
}

參考來源

julian_avatar

Julian Hsu

Software Engineer

Give me a cup of Milk Cap Tea and I can lift the whole world. Must be full sugar and Cream Cheese flavor. Preferably topped with a Strawberry Mochi.

Check more from this author

Share to

Back