前言
本篇文章我们来使用 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 会自动填充,但是 email 需要在前台带入
接口设计
这里以 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 文件:
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 } } })
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 } })
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 } })
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: '请求成功' } })
前端调用方式
html 代码:<script setup lang="ts"> const { data } = await useFetch('/api/websites') </script> <template> <pre>{{ data }}</pre> </template>
接口的相关逻辑,自己可以根据实际情况修改,具体的数据库操作文档可参考: Supabase API DOCS
效果预览
总结
本篇文章我们学到了以下知识:
到这里,项目的整体框架就已经出来了,后续我们要做的就是添加数据和完善优化,并根据自己爱好添加一些自己喜欢的功能。
Github 仓库:dream-site
线上预览:dream-site.cn