使用 SvelteKit 和 OceanBase 构建全栈应用

1,479 阅读8分钟

关于 OceanBase 和 SvelteKit

SvelteKit 是一个构建在 Svelte 之上的框架(类似于 Next.js 与 React 的关系)。它旨在构建各种规模的 Web 应用程序,具有基于文件系统的灵活路由。与其他单页应用程序不同,SvelteKit 不会在 SEO、渐进式增强或初始加载体验上妥协——但与传统的服务器呈现应用程序不同,导航是即时的,具有类似应用程序的感觉。

SvelteKit 和 OceanBase 是一对动态组合。OceanBase是蚂蚁集团开发的分布式关系型数据库管理系统。它旨在处理极大量的数据并提供高可用性和可扩展性。

OceanBase 简化了构建和部署云原生应用程序的过程,显着缩短了部署时间。借助 OceanBase 强大的分布式架构,您可以更快、更高效地构建应用程序,同时确保跨区域的可靠性和性能。

我们要构建什么:Todo 应用程序

我们将构建一个允许用户创建、读取、更新和删除任务的 Todo 应用程序。该应用程序将使用 SvelteKit 作为前端和服务器,并使用 OceanBase 作为数据库。由于OceanBase兼容MySQL,我们可以使用MySQL客户端连接服务器端和OceanBase数据库实例。

这个使用 SvelteKit 和 OceanBase 构建的 Todo 应用程序是如何在项目中使用 OceanBase 的一个很好的例子。尽管 OceanBase 通常用于更复杂的用例,如银行、欺诈检测和保险,但该项目概述了如何在使用 SvelteKit 构建的全栈应用程序中实施 OceanBase,包括设置必要的组件和路由连接到数据库并执行 CRUD 操作。

任务数据将存储在 AWS 中 EC2 实例上的 OceanBase 数据库中。这是最终应用程序的样子。我还将在文章末尾包含一个GitLab存储库,以便您自己试用。

图片描述

本项目使用的技术包括:

  • OceanBase , 一个分布式关系数据库
  • SvelteKit,一个基于 Svelte 的全栈 Web 框架
  • Tailwind CSS,一个实用程序优先的 CSS 框架,用于设置前端组件的样式
  • MySQL2,用于 Node.js 的 MySQL 驱动程序

设置 OceanBase

要设置项目,您首先需要一个正在运行的 OceanBase 集群。为此,您有多种选择。您可以在本地环境中安装 OceanBase在云中启动虚拟机来运行它,或者使用AWS 市场中的 OceanBase Cloud只需点击几下即可设置您的集群。

在此项目中,为简单起见,我将使用 EC2 方法和演示服务器。在生产中,请参阅 OceanBase 的关于在 Kubernetes 集群中部署的官方指南。

运行演示服务器后,我创建了一个密码为“demo”的演示用户,该用户只能访问todos我为该项目创建的数据库。

下面是任务表的示例架构:

CREATE TABLE tasks (
  id INT NOT NULL AUTO_INCREMENT,
  text VARCHAR(255) NOT NULL,
  completed BOOLEAN NOT NULL DEFAULT false,
  PRIMARY KEY (id)
);

这将创建一个包含三列的任务表:(id一个自动递增的整数),text(一个最多 255 个字符的字符串,这是待办事项的名称)和completed(一个默认为 false 的布尔值)。表的主键是id列。

设置 SvelteKit

要开始使用 SvelteKit,您需要在计算机上安装 Node.js。您可以从官方网站下载最新版本的 Node.js。

安装 Node.js 后,您可以使用以下命令创建一个新的 SvelteKit 项目:

npx degit sveltejs/kit oceanbase-app
cd oceanbase-app
npm install

这将在目录中创建一个新的 SvelteKit 项目oceanbase-app并安装所有必要的依赖项。

现在,运行以下命令启动开发服务器。

npm run dev

这将启动服务器并允许您在浏览器中查看您的应用程序http://localhost:3000

用户界面

Svelte 是一个基于组件的框架,这意味着我们需要在文件夹中创建一些 Svelte 组件src。在该components文件夹中,我们需要创建两个 Svelte 组件:Todo.svelteTodoForm.svelte。该Todo组件将显示一个任务列表,该TodoForm组件将允许用户创建和更新任务。

这是 Todo.svelte组件。它负责在屏幕上呈现单个待办事项,包括用于将任务标记为已完成的复选框、任务文本和用于从列表中删除任务的“删除”按钮。

该组件接收一个todo对象作为道具,其中包含有关任务的信息,例如其 ID、文本和完成状态。toggleTodoCompleted当用户单击复选框时调用该函数,当deleteTodo用户单击“删除”按钮时调用该函数。这两个函数都是从文件中导入的todoStore.js,该文件处理 OceanBase 数据库中待办事项的 CRUD 操作。

<script>
    export let todo;
    import {deleteTodo, toggleTodoCompleted} from '../stores/todoStore.js';
</script>

<li class="bg-white flex items-center shadow-sm border border-gray-200 rounded-lg my-2 py-2 px-4">
    <input
        name="completed"
        type="checkbox"
        checked={todo.completed}
        on:change={() => toggleTodoCompleted(todo.id, todo.completed)}
        class="mr-2 form-checkbox h-5 w-5"
    />
    <span
        class={`flex-1 text-gray-800  ${
            todo.completed ? 'line-through' : ''
        }`}
    >
        {todo.text}
    </span>
    <button
        type="button"
        class="text-sm bg-red-500 hover:bg-red-600 text-white py-1 px-2 rounded focus:outline-none focus:shadow-outline"
        on:click={() => deleteTodo(todo.id)}
    >
        Delete
    </button>
</li>

这是TodoForm.svelte组件。addTodo它从文件中导入一个函数todoStore.js,该函数处理 OceanBase 数据库中待办事项的 CRUD 操作。该表单包括供用户输入任务文本的标签和输入字段,以及用于将任务添加到应用程序的提交按钮。

<script>
    import { addTodo } from '../stores/todoStore.js';
    let todo = '';

    const handleSubmit = () => {
        addTodo(todo);
        todo = '';
    };
</script>

<form class="form my-6" on:submit|preventDefault={handleSubmit}>
    <div class="flex flex-col text-sm mb-2">
        <label for="todo" class="font-bold mb-2 text-gray-800 "> Todo </label>
        <input
            type="text"
            name="todo"
            bind:value={todo}
            placeholder="ex. Learn about authentication in Next.js"
            class="appearance-none shadow-sm border border-gray-200 p-2 focus:outline-none focus:border-gray-500 rounded-lg "
        />
    </div>
    <button
        type="submit"
        class=" w-full shadow-sm rounded bg-green-500 hover:bg-green-600 text-white py-2 px-4"
    >
        Submit
    </button>
</form>

然后我们需要在页面上显示所有的待办事项。我们将使用默认索引页来执行此操作。在/src/route/index.svelte页面中,我们可以遍历所有的待办事项并显示出来。

<script>
    import Todo from '../components/Todo.svelte';
    import TodoForm from '../components/TodoForm.svelte';
    import { todos } from '../stores/todoStore.js';
</script>

<main>
    <h1 class="text-2xl font-bold text-center text-gray-800 md:text-3xl">
        Todo App
    </h1>
    <TodoForm />
    {#each $todos as todo (todo.id)}
        <Todo {todo} />
    {/each}
</main>

src/stores/todoStore.js文件中,我们有添加、删除和更新任务的功能。这些函数连接到 OceanBase 数据库并执行必要的操作以创建、读取、更新和删除任务。

loadTodos函数从数据库加载任务并todos使用数据更新存储。、和函数连接到 OceanBase 数据库并执行必要的操作以创建、删除和更新任务addTodo。通过使用这些函数,我们可以通过 UI 对任务进行 CRUD 操作。deleteTodo``toggleTodoCompleted

请注意,出于安全原因,这些函数与/api/todosAPI 路由对话,而不是直接与数据库对话。在这种情况下,数据库查询在服务器端安全运行。我将在本文的下一部分深入探讨 API 路由。

import { writable } from 'svelte/store';

export const todos = writable([]);

export const loadTodos = async () => {
    try {
        const response = await fetch('/api/todos');
        const data = await response.json();
        todos.set(data);
    } catch (error) {
        console.error('Error fetching data:', error);
    }
};
loadTodos();

export const addTodo = async (text) => {
    try {
        const response = await fetch('/api/todos', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
            },
            body: JSON.stringify({ text: text, completed: false }),
        });

        const data = await response.json();
        todos.update((currentTodos) => [...currentTodos, data]);
    } catch (error) {
        console.error('Error adding todo:', error);
    }
};

export const deleteTodo = async (id) => {
    try {
        await fetch(`/api/todos/${id}`, {
            method: 'DELETE',
        });

        todos.update((currentTodos) =>
            currentTodos.filter((todo) => todo.id !== id)
        );
    } catch (error) {
        console.error('Error deleting todo:', error);
    }
};

export const toggleTodoCompleted = async (id, currentState) => {
    try {
        const response = await fetch(`/api/todos/${id}`, {
            method: 'PUT',
            headers: {
                'Content-Type': 'application/json',
            },
            body: JSON.stringify({ completed: !currentState }),
        });

        const data = await response.json();
        todos.update((currentTodos) =>
            currentTodos.map((todo) => (todo.id === id ? data : todo))
        );
    } catch (error) {
        console.error('Error toggling todo completed status:', error);
    }
};

连接 SvelteKit 和 OceanBase

现在让我们谈谈服务器端代码。服务器端由两部分组成:在服务器和我们正在运行的 OceanBase 实例之间建立连接的数据库模块,以及从服务器在 OceanBase 数据库中执行 CRUD 操作的 API 路由。

在该lib/db/oceanbase.js文件中,我们可以使用 MySQL2 客户端与 OceanBase 建立连接,然后导出连接供我们的 API 路由使用。将数据库凭据存储在.env文件中并使用dotenv包访问这些值始终是一个好习惯。

import mysql from 'mysql2/promise';
import * as dotenv from 'dotenv';
dotenv.config();
export const oceanbaseConnection = await mysql.createConnection({
    host: process.env.HOST,
    port: 2881,
    user: process.env.USERNAME,
    database: 'todos',
});

src/routes/api文件夹中,我们有一个index.js和一个[id].js文件。这些文件包含我们的应用程序将用来与 OceanBase 数据库交互的 API 端点。该index.js文件处理所有待办事项的 CRUD 操作,而该[id].js文件按 ID 处理单个待办事项的 CRUD 操作。

index.js文件中,我们首先需要从我们的数据库模块中导入oceanbaseConnection对象并编写 GET 和 POST 请求函数。这两个函数将允许前端从数据库中获取所有待办事项并将新的待办事项插入数据库。

import { oceanbaseConnection } from '../../../lib/db/oceanbase';

对于 GET 请求,我们需要对数据库的任务表进行 SELECT everything SQL 查询。

export async function get() {
    let results = await oceanbaseConnection
        .query('SELECT * FROM tasks')
        .then(function ([rows, fields]) {
            console.log(rows);
            return rows;
        });

    return {
        body: results,
    };
}

对于 POST 方法,我们需要从请求中获取文本和完成的属性,并将它们合并到一个 INSERT SQL 语句中,该语句将一行插入到任务表中。

export async function post(request) {
    console.log(request);
    const { text, completed } = request.body;
    let newTask = await oceanbaseConnection
        .query('INSERT INTO tasks (text, completed) VALUES (?, ?)', [
            text,
            completed,
        ])
        .then(function ([result]) {
            return { id: result.insertId, text: text, completed: completed };
        });
    return {
        body: newTask,
    };
}

从 UI 添加一些待办事项后,我们可以在 OceanBase 数据库中看到以下行:

图片描述

同样在[id].js文件中,我们需要设置一个 DELETE 和一个 PUT 函数,以便前端可以执行删除操作,或切换待办事项的完成状态。

首先,让我们也oceanbaseConnection从数据库模块中导入对象。

import { oceanbaseConnection } from '../../../lib/db/oceanbase';

在该[id].js文件中,实现了一个 DELETE 方法以从任务表中删除一个项目。这是通过使用id来自请求的参数的 DELETE FROM SQL 语句来实现的。

export async function del(req) {
    const { id } = req.params;
    try {
        await oceanbaseConnection.query('DELETE FROM tasks WHERE id = ?', [id]);
        return {
            status: 200,
            body: { message: `Todo with id ${id} deleted successfully.` },
        };
    } catch (error) {
        console.error('Error deleting todo:', error);
        return {
            status: 500,
            body: { message: 'Error deleting todo. Please try again later.' },
        };
    }
}

实施 PUT 方法以更新任务表中待办事项的完成状态。我们将使用 UPDATE 语句来更新数据库中的项目。


export async function put(req) {
    const { id } = req.params;
    const { completed } = req.body;

    try {
        await oceanbaseConnection.query(
            'UPDATE tasks SET completed = ? WHERE id = ?',
            [completed, id]
        );
        const [updatedTodo] = await oceanbaseConnection.query(
            'SELECT * FROM tasks WHERE id = ?',
            [id]
        );
        return {
            status: 200,
            body: JSON.stringify(updatedTodo),
        };
    } catch (error) {
        console.error('Error toggling todo completed status:', error);
        return {
            status: 500,
            body: {
                message:
                    'Error toggling todo completed status. Please try again later.',
            },
        };
    }
} 

结论

总之,使用 SvelteKit 和 OceanBase 构建全栈应用程序是学习如何在项目中使用 OceanBase 的好方法。通过设置必要的组件和路由,连接数据库,进行CRUD操作,您可以更好地了解如何在实际环境中使用OceanBase。借助此 Todo 应用程序,您可以探索 OceanBase 的功能并了解它如何与您的 Node 应用程序结合使用。