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

前言

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

表设计

这里需要用到两张表:

  1. ds_categorys:存储网站分类
列名 类型 备注
id uuid 主键,分类 id
name text 分类名称
desc text 分类描述
sort int2 排序
  1. ds_websites:存储网站分类子站点
列名 类型 备注
id uuid 主键,站点 id
name text 站点名称
desc text 站点描述
category_id uuid 所属分类 id
url text 站点 url
logo text 站点 logo
tags text 站点标签
sort int2 排序

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

列名 类型 备注
user_id auth.uid() 登录用户的 uuid
email text 登录用户的 email

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

接口设计

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

接口 Methods 备注
/api/websites Get 读取
/api/websites Post 新增
/api/websites Put 更新
/api/websites Delete 删除

前端实现

阅读 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
     }
   }
 })
  1. 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
   }
 })
  1. 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
   }
 })
  1. 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: '请求成功'
   }
 })
  1. 前端调用方式
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

相关推荐
TE-茶叶蛋6 小时前
Vue Fragment vs React Fragment
javascript·vue.js·react.js
Angindem6 小时前
从零搭建uniapp项目
前端·vue.js·uni-app
前端小白从0开始8 小时前
Vue3项目实现WPS文件预览和内容回填功能
前端·javascript·vue.js·html5·wps·文档回填·文档在线预览
難釋懷8 小时前
Vue解决开发环境 Ajax 跨域问题
前端·vue.js·ajax
特立独行的猫a8 小时前
Nuxt.js 中的路由配置详解
开发语言·前端·javascript·路由·nuxt·nuxtjs
挑战者6668889 小时前
vue入门环境搭建及demo运行
前端·javascript·vue.js
程序猿ZhangSir11 小时前
Vue3 项目的基本架构解读
前端·javascript·vue.js
亲亲小宝宝鸭12 小时前
写了两个小需求,终于搞清楚了表格合并
前端·vue.js
Face12 小时前
路由Vue-router 及 异步组件
前端·javascript·vue.js
风之舞_yjf13 小时前
Vue基础(14)_列表过滤、列表排序
前端·javascript·vue.js