前言
本篇文章我们来使用 Supabase 实现 RESTful
风格的 API
接口,以此来实现网站分类和子站点的 CURD
功能。
表设计
这里需要用到两张表:
ds_categorys
:存储网站分类
列名 | 类型 | 备注 |
---|---|---|
id | uuid | 主键,分类 id |
name | text | 分类名称 |
desc | text | 分类描述 |
sort | int2 | 排序 |
ds_websites
:存储网站分类子站点
列名 | 类型 | 备注 |
---|---|---|
id | uuid | 主键,站点 id |
name | text | 站点名称 |
desc | text | 站点描述 |
category_id | uuid | 所属分类 id |
url | text | 站点 url |
logo | text | 站点 logo |
tags | text | 站点标签 |
sort | int2 | 排序 |
这里需要注意的是,因为 Supabase 使用的是 postgresql 的 Row Level Security (RLS),一些数据库的操作对应不同的策略,这里我们还应该为每张表加上两个字段:
列名 | 类型 | 备注 |
---|---|---|
user_id | auth.uid() | 登录用户的 uuid |
text | 登录用户的 email |
数据录入的时候
user_id
会自动填充,但是
接口设计
这里以 ds_websites
表为例,前台需要实现 CURD
功能,为此我们把接口设计成 RESTful
风格:
接口 | Methods | 备注 |
---|---|---|
/api/websites | Get | 读取 |
/api/websites | Post | 新增 |
/api/websites | Put | 更新 |
/api/websites | Delete | 删除 |
前端实现
阅读 Nuxt3 中文文档,我们可以在 server/api
目录下新增接口。
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) * pageSizeconst end = current * pageSize - 1// 查询 sqllet 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}}})
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}})
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}})
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: '请求成功'}})
- 前端调用方式
<script setup lang="ts">
const { data } = await useFetch('/api/websites')
</script><template><pre>{{ data }}</pre>
</template>
接口的相关逻辑,自己可以根据实际情况修改,具体的数据库操作文档可参考: Supabase API DOCS
效果预览
总结
本篇文章我们学到了以下知识:
- Nuxt3 如何创建接口并调用
- Supabase 数据库的基本操作和表的创建
到这里,项目的整体框架就已经出来了,后续我们要做的就是添加数据和完善优化,并根据自己爱好添加一些自己喜欢的功能。
Github 仓库
:dream-site
线上预览
:dream-site.cn