网站LOGO
白雾茫茫丶
页面加载中
10月18日
网站LOGO 白雾茫茫丶
记录学习、生活和有趣的事
菜单
  • 白雾茫茫丶
    记录学习、生活和有趣的事
    用户的头像
    首次访问
    上次留言
    累计留言
    我的等级
    我的角色
    打赏二维码
    打赏博主
    Nuxt3 实战 (十):使用 Supabase 实现 RESTful 风格 API 接口
    点击复制本页信息
    微信扫一扫
    文章二维码
    文章图片 文章标题
    创建时间
  • 一 言
    确认删除此评论么? 确认
  • 本弹窗介绍内容来自,本网站不对其中内容负责。
    • 复制图片
    • 复制图片地址
    • 百度识图
    按住ctrl可打开默认菜单

    Nuxt3 实战 (十):使用 Supabase 实现 RESTful 风格 API 接口

    谢明伟 · 原创 ·
    前端开发Nuxt3 实战 · VueNuxt
    共 7339 字 · 约 2 分钟 · 481
    本文最后更新于2024年06月19日,已经过了120天没有更新,若内容或图片失效,请留言反馈

    前言

    本篇文章我们来使用 Supabase 实现 RESTful 风格的 API 接口,以此来实现网站分类和子站点的 CURD 功能。

    表设计

    这里需要用到两张表:

    1. ds_categorys:存储网站分类
    列名类型备注
    iduuid主键,分类 id
    nametext分类名称
    desctext分类描述
    sortint2排序
    1. ds_websites:存储网站分类子站点
    列名类型备注
    iduuid主键,站点 id
    nametext站点名称
    desctext站点描述
    category_iduuid所属分类 id
    urltext站点 url
    logotext站点 logo
    tagstext站点标签
    sortint2排序

    这里需要注意的是,因为 Supabase 使用的是 postgresqlRow Level Security (RLS),一些数据库的操作对应不同的策略,这里我们还应该为每张表加上两个字段:

    列名类型备注
    user_idauth.uid()登录用户的 uuid
    emailtext登录用户的 email

    数据录入的时候 user_id 会自动填充,但是 email 需要在前台带入

    接口设计

    这里以 ds_websites 表为例,前台需要实现 CURD 功能,为此我们把接口设计成 RESTful 风格:

    接口Methods备注
    /api/websitesGet读取
    /api/websitesPost新增
    /api/websitesPut更新
    /api/websitesDelete删除

    前端实现

    阅读 Nuxt3 中文文档,我们可以在 server/api 目录下新增接口。

    1. Get 接口server/api 目录下新建 index.get.ts 文件:

      ts 代码:
       import type { Response, PageResponse, WebsiteList, WebsiteParams } from '~/types'
       import { serverSupabaseClient } from '#supabase/server'
       import { RESPONSE_STATUS_CODE } from '~/enum'
      
       export default defineEventHandler(async (event): Promise<Response<PageResponse<WebsiteList>>> => {
      const client = await serverSupabaseClient(event)
      // 获取请求参数
      const { current, pageSize, name = '', category_id = '' } = getQuery(event) as WebsiteParams
      // 判断参数
      if (!current || !pageSize) {
       return { code: RESPONSE_STATUS_CODE.FAIL, msg: '参数错误' }
      }
      
      // 计算分页
      const start = (current - 1) * pageSize
      const end = current * pageSize - 1
      
      // 查询 sql
      let sqlQuery = client
       .from('ds_websites')
       .select('*,ds_categorys(*)', { count: 'exact' })
       .range(start, end)
       .order('sort', {
         ascending: false
       })
       .order('created_at', {
         ascending: false
       })
      
      // 判断查询参数
      if (name) {
       sqlQuery = sqlQuery.like('name', `%${name}%`)
      }
      if (category_id) {
       sqlQuery = sqlQuery.eq('category_id', category_id)
      }
      
      // 请求列表
      const { data, error, count } = await sqlQuery
      
      // 判断请求结果
      if (error) {
       throw createError({
         statusCode: RESPONSE_STATUS_CODE.FAIL,
         statusMessage: error.message
       })
      }
      
      // 请求成功
      return {
       code: RESPONSE_STATUS_CODE.SUCCESS,
       msg: '请求成功',
       data: {
         list: data,
         total: count
       }
      }
       })
    2. Post 接口server/api 目录下新建 index.post.ts 文件:

      ts 代码:
       import type { Response, WebsiteEdit, WebsiteList } from '~/types'
       import { serverSupabaseClient, serverSupabaseUser } from '#supabase/server'
       import { RESPONSE_STATUS_CODE } from '~/enum'
      
       export default defineEventHandler(async (event): Promise<Response<WebsiteList[]>> => {
      const client = await serverSupabaseClient<WebsiteList>(event)
      const user = await serverSupabaseUser(event)
      // 得到请求体
      const body: WebsiteEdit = await readBody(event)
      
      // 插入数据
      const { data, error } = await client
       .from('ds_websites')
       .insert({ ...body, email: user?.email })
       .select()
      
      // 判断请求结果
      if (error) {
       // 23505 是 PostgreSQL 的唯一性违反错误码
       if (error.code === '23505') {
         return {
           code: RESPONSE_STATUS_CODE.FAIL,
           msg: '站点名称已存在!'
         }
       } else {
         throw createError({
           statusCode: RESPONSE_STATUS_CODE.FAIL,
           statusMessage: error.message
         })
       }
      }
      
      // 请求成功
      return {
       code: RESPONSE_STATUS_CODE.SUCCESS,
       msg: '请求成功',
       data: data
      }
       })
    3. Put 接口server/api 目录下新建 index.put.ts 文件:

      ts 代码:
       import type { Response, WebsiteEdit, WebsiteList } from '~/types'
       import { serverSupabaseClient } from '#supabase/server'
       import { RESPONSE_STATUS_CODE } from '~/enum'
      
       export default defineEventHandler(async (event): Promise<Response<WebsiteList[]>> => {
      const client = await serverSupabaseClient<WebsiteList>(event)
      // 得到请求体
      const { id, ...body }: WebsiteEdit = await readBody(event)
      
      if (!id) {
       return {
         code: RESPONSE_STATUS_CODE.FAIL,
         msg: 'id不能为空!'
       }
      }
      
      // 插入数据
      const { data, error } = await client
       .from('ds_websites')
       .update({ ...body, updated_at: new Date() })
       .eq('id', id)
       .select()
      
      // 判断请求结果
      if (error) {
       // 23505 是 PostgreSQL 的唯一性违反错误码
       if (error.code === '23505') {
         return {
           code: RESPONSE_STATUS_CODE.FAIL,
           msg: '站点名称已存在!'
         }
       } else {
         throw createError({
           statusCode: RESPONSE_STATUS_CODE.FAIL,
           statusMessage: error.message
         })
       }
      }
      
      // 请求成功
      return {
       code: RESPONSE_STATUS_CODE.SUCCESS,
       msg: '请求成功',
       data: data
      }
       })
    4. Delete 接口server/api 目录下新建 index.delete.ts 文件:

      ts 代码:
       import type { Response, WebsiteEdit, WebsiteList } from '~/types'
       import { serverSupabaseClient } from '#supabase/server'
       import { RESPONSE_STATUS_CODE } from '~/enum'
      
       export default defineEventHandler(async (event): Promise<Response<WebsiteList[]>> => {
      const client = await serverSupabaseClient<WebsiteList>(event)
      // 得到请求体
      const { id }: WebsiteEdit = await readBody(event)
      
      if (!id) {
       return {
         code: RESPONSE_STATUS_CODE.FAIL,
         msg: 'id不能为空!'
       }
      }
      
      // 删除数据
      const { error } = await client.from('ds_websites').delete().eq('id', id)
      
      // 判断请求结果
      if (error) {
       throw createError({
         statusCode: RESPONSE_STATUS_CODE.FAIL,
         statusMessage: error.message
       })
      }
      
      // 请求成功
      return {
       code: RESPONSE_STATUS_CODE.SUCCESS,
       msg: '请求成功'
      }
       })
    5. 前端调用方式

      html 代码:
      <script setup lang="ts">
      const { data } = await useFetch('/api/websites')
      </script>
      
      <template>
       <pre>{{ data }}</pre>
      </template>

    接口的相关逻辑,自己可以根据实际情况修改,具体的数据库操作文档可参考: Supabase API DOCS

    效果预览

    总结

    本篇文章我们学到了以下知识:

    1. Nuxt3 如何创建接口并调用
    2. Supabase 数据库的基本操作和表的创建

    到这里,项目的整体框架就已经出来了,后续我们要做的就是添加数据和完善优化,并根据自己爱好添加一些自己喜欢的功能。

    Github 仓库dream-site

    线上预览dream-site.cn

    声明:本文由 谢明伟(博主)原创,依据 CC-BY-NC-SA 4.0 许可协议 授权,转载请注明出处。

    还没有人喜爱这篇文章呢

    我要发表评论 我要发表评论
    博客logo 白雾茫茫丶 记录学习、生活和有趣的事 51统计 百度统计
    MOEICP 萌ICP备20236860号 ICP 粤ICP备2023007649号 ICP 粤公网安备44030402006402号

    💻️ 谢明伟 3小时前 在线

    🕛

    本站已运行 2 年 291 天 2 小时 4 分

    🌳

    自豪地使用 Typecho 建站,并搭配 MyLife 主题
    白雾茫茫丶. © 2022 ~ 2024.
    网站logo

    白雾茫茫丶 记录学习、生活和有趣的事