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

前言

本篇文章我们来使用 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 文件:
 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 文件:
 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 文件:
 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 文件:
 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. 前端调用方式
<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

  • 17
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

白雾茫茫丶

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值