Nuxt Server Api
阅读时长:15分钟
本文内容: 国内关于Nuxt3的资料太少了,而Nuxt3又发布了没有多久,导致资料太少。本文浓缩讲解了,对于一个前端开发,上手使用 Nuxt3,并一个人承担前后端开发的所有须知内容
- [Nuxt3 + Vue3 + Typescript + TaiwindCss 企业级模板]: 即将发布
环境要求:
- node :v16 +
- redis : v6 +
export default defineEventHandler(async (event) => {
}
- Nuxt3.js 官方文档
- Tailwindcss 文档
- Antdesign Vue 文档
起步
- 首先要把官方文档
关于目录的解释
看一遍: nuxt3 目录 - 前端页面的开发,只需聚焦目录
pages
,所有写法与传统Vue3一模一样. - 后端接口的开发,只需聚焦目录
server
, 重点讲解服务端
目录 Layout
一旦设置 Layout,那么文档结构被分为三部分. 头部,content内容主体,页脚
<template>
<div class="overflow-x-hidden">
<LayoutPageNavbar class="h-[64px] max-h-[64px]" />
<LayoutPageContent>
<slot />
</LayoutPageContent>
<LayoutPageFooter class="h-[52px] md:h-[42px]" />
</div>
</template>
服务端 H3设置
Nuxt3的服务端使用Nitro构建,与H3紧密结合
pnpm add h3
- 设置Http状态
import { H3Event } from "h3"
export default defineEventHandler(async (event: H3Event) => {
setResponseStatus(event, 204)
return {
data: '',
code: 0,
msg: 'success'
}
}
- 设置 Cookie
const AUTH_COOKIE_NAME = '__session'
const AUTH_COOKIE_MAX_AGE = 60 * 60 * 24 * 5 * 1_000
export default defineEventHandler(async (event: H3Event) => {
const cookie = 'ffkeifpoeapoifm321654'
setCookie(event, AUTH_COOKIE_NAME, cookie, {
maxAge: AUTH_COOKIE_MAX_AGE,
secure: true,
httpOnly: true,
path: '/',
sameSite: 'lax',
})
return {
data: '',
code: 0,
msg: 'success'
}
}
- 存储
interface Query {
region?: 'zh-CN' | 'en-US' | 'ja-JP' | 'en-AU' | 'en-UK' | 'de-DE' | 'en-NZ' | 'en-CA'
}
export default defineEventHandler(async (event) => {
const { type = 'img', region = 'zh-CN' } = getQuery<Query>(event)
if (type === 'img') {
const cache = await useStorage('cache').getItem('bing-wallpaper')
if (cache)
return await sendRedirect(event, cache, 302)
}
// https://github.com/zkeq/Bing-Wallpaper-Action
const data = await (await fetch(`https://raw.onmicrosoft.cn/Bing-Wallpaper-Action/main/data/${region}_all.json`)).json()
if (type === 'img') {
const url = `https://bing.com${data.data[0].url}`
useStorage('cache').setItem('cache:bing-wallpaper', url, { ttl: getTodayRemainMillisecond() })
return await sendRedirect(event, url, 302)
// event.node.res.setHeader('Content-Type', 'image/png;charset=utf-8')
// return Buffer.from(await (await fetch(url)).arrayBuffer())
}
else {
event.context.cache = { ttl: TimeUnitMap.hour }
return data
}
})
- 验证
import { z, useValidatedBody } from 'h3-zod'
export default defineEventHandler(async event => {
const body = await useValidatedBody(
event,
z.object({
userId: z.string(),
})
)
})
- 返回Error
export default defineEventHandler(async (event) => {
try {
const cookie = getCookie(event, 'refresh_token') as Token;
await deleteRefreshToken(cookie);
return {
cookie,
};
} catch (error) {
sendError(
event,
createError({
statusCode: 400,
statusMessage: 'username or password is invalid',
})
);
}
sendRefreshToken(event, '');
return {
message: 'Logout successful',
};
});
- 设置返回数据类型
- 返回
audio
类型数据
服务端设置 header
// server/api/speech.ts
export default defineEventHandler(async (event: H3Event) => {
try {
// 获取音频数据
const audioBlob = await fetch_to_audio()
let size = 0;
if (audioBlob instanceof Blob) {
size = audioBlob.size
}
setResponseHeader(event, 'Accept-Ranges', 'bytes')
setResponseHeader(event, 'Content-Type', 'audio/wav')
setResponseHeader(event, 'Content-Length', size)
return audioBlob;
} catch (error) {
sendError(
event,
createError({
statusCode: 400,
statusMessage: 'username or password is invalid',
})
);
}
})
前端(客户端)设置数据返回类型为blob
<script lang="ts" setup>
const createAudio = async () => {
const response: any = await axios({
url: '/api/speech',
method: "POST",
data: {
content: '123456',
},
responseType: "blob"
})
const { data: arrayBuffer } = response
console.log('------收到服务端数据------', response)
const box = document.querySelector("#audio-box") as HTMLElement
const audioElement = document.createElement("audio"); //创建标签
audioElement.autoplay = true
audioElement.controls = true
audioElement.src = URL.createObjectURL(arrayBuffer); // 指定链接
box.appendChild(audioElement)
}
</script>
<template>
<div>
<figure id="audio-box">
<figcaption>音频:</figcaption>
</figure>
</div>
</template>
- 返回
image
图片类型数据
服务端设置 header
// server/api/image.ts
export default defineEventHandler(async (event: H3Event) => {
try {
// 获取图片
const image = await fetch_to_image()
let size = 0;
if (audioBlob instanceof Blob) {
size = audioBlob.size
}
setHeader(e, 'Content-Type', 'image/png')
return image
} catch (error) {
sendError(
event,
createError({
statusCode: 400,
statusMessage: 'username or password is invalid',
})
);
}
})
SQL使用
- postgres 数据库
import { createPool, sql } from '@vercel/postgres'
async function seed() {
const createTable = await sql`
CREATE TABLE IF NOT EXISTS users (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
email VARCHAR(255) UNIQUE NOT NULL,
image VARCHAR(255),
"createdAt" TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);
`
console.log(`Created "users" table`)
const users = await Promise.all([
sql`
INSERT INTO users (name, email, image)
VALUES ('Guillermo Rauch', 'rauchg@vercel.com', 'https://pbs.twimg.com/profile_images/1576257734810312704/ucxb4lHy_400x400.jpg')
ON CONFLICT (email) DO NOTHING;
`,
sql`
INSERT INTO users (name, email, image)
VALUES ('Lee Robinson', 'lee@vercel.com', 'https://pbs.twimg.com/profile_images/1587647097670467584/adWRdqQ6_400x400.jpg')
ON CONFLICT (email) DO NOTHING;
`,
sql`
INSERT INTO users (name, email, image)
VALUES ('Steven Tey', 'stey@vercel.com', 'https://pbs.twimg.com/profile_images/1506792347840888834/dS-r50Je_400x400.jpg')
ON CONFLICT (email) DO NOTHING;
`,
])
console.log(`Seeded ${users.length} users`)
return {
createTable,
users,
}
}
export default defineEventHandler(async () => {
const startTime = Date.now()
const db = createPool()
try {
const { rows: users } = await db.query('SELECT * FROM users')
const duration = Date.now() - startTime
return {
users: users,
duration: duration,
}
} catch (error) {
// @ts-ignore
if (error?.message === `relation "users" does not exist`) {
console.log(
'Table does not exist, creating and seeding it with dummy data now...'
)
// Table is not created yet
await seed()
const { rows: users } = await db.query('SELECT * FROM users')
const duration = Date.now() - startTime
return {
users: users,
duration: duration,
}
} else {
throw error
}
}
})
- mysql2
安装 Antdesign Vue两种方式
- 方式一,模块方式
# Using pnpm
pnpm add -D @ant-design-vue/nuxt
nuxt.config.ts
export default defineNuxtConfig({
modules: [
'@ant-design-vue/nuxt'
],
antd:{
// Options
}
})
使用
<script lang="ts" setup>
const handleMessage = () => {
message.info("This is a normal message");
}
</script>
<template>
<a-button @click="handleMessage">
button
</a-button>
</template>
- 方式二,插件方式
# Using pnpm
pnpm add -D ant-design-vue
在plugins文件中新建
awesome.ts
// 1. 引入组件
import Antd from 'ant-design-vue';
// 2. 引入组件样式
import 'ant-design-vue/dist/antd.css';
export default defineNuxtPlugin((nuxt) => {
nuxt.vueApp.use(Antd);
})
使用
<script lang="ts" setup>
const handleMessage = () => {
message.info("This is a normal message");
}
</script>
<template>
<a-button @click="handleMessage">
button
</a-button>
</template>
Server Storage 服务端存储
Nuxt的服务端通常只作为转发/代理层,因此数据存储通常也使用轻量的 Redis 存储
- 操作系统安装Redis
- Redis集成有两种方式
方式一:通过手写代码集成
server/plugins/storage.ts
import redisDriver from 'unstorage/drivers/redis'
export default defineNitroPlugin(() => {
const storage = useStorage()
// Dynamically pass in credentials from runtime configuration, or other sources
const driver = redisDriver({
base: 'redis',
host: useRuntimeConfig().redis.host,
port: useRuntimeConfig().redis.port,
/* other redis connector options */
})
// Mount driver
storage.mount('redis', driver)
})
nuxt.config.ts
export default defineNuxtConfig({
runtimeConfig: {
redis: { // Default values
host: '',
port: 0,
/* other redis connector options */
}
}
})
方式二:通过nuxt.内置的nitro服务,nitro.storage
nuxt.config.ts
export default defineNuxtConfig({
nitro: {
storage: {
'redis': {
driver: 'redis',
/* redis connector options */
port: 6379, // Redis port
host: "127.0.0.1", // Redis host
username: "", // needs Redis >= 6
password: "",
db: 0, // Defaults to 0
tls: {} // tls/ssl
}
}
}
})
谷歌&github 登录
import { NuxtAuthHandler } from "#auth";
import GithubProvider from "next-auth/providers/github";
import GoogleProvider from "next-auth/providers/google";
const nuxtAuthHandler = NuxtAuthHandler({
secret: useRuntimeConfig().authSecret,
providers: [
// @ts-ignore
GithubProvider.default({
clientId: useRuntimeConfig().githubClientId,
clientSecret: useRuntimeConfig().githubClientSecret,
httpOptions: {
timeout: 10000,
},
}),
// @ts-ignore
GoogleProvider.default({
clientId: useRuntimeConfig().googleClientId,
clientSecret: useRuntimeConfig().googleClientSecret,
}),
],
});
export default defineEventHandler(async (event) => {
const result = await nuxtAuthHandler(event);
const headerCookies = event.node.res.getHeader("set-cookie");
if (headerCookies && typeof headerCookies === "string") {
const sessionToken = headerCookies
.split(/,(?!\s)/)
.find((v) => v.includes("session-token"));
if (sessionToken) {
event.node.res.removeHeader("set-cookie");
event.node.res.setHeader("set-cookie", sessionToken);
}
}
return result;
});
参考文档
参考API
- H3 API document
- H3 github
- H3 1.8 API latest intro
参考例子
- 例子
- About基于Nuxt3 + Vite3 + Vue3 + UnoCSS搭建的API接口服务网站
- 参考项目
- audio返回数据
- Base64 、Blob、File之间的相互转换
- NUXT3 server/api params cause 500 error on production)
- How to create an AudioBuffer from a Blob
- How to return image with server api
- How to return audio data with server api
------ 如果文章对你有用,感谢右上角 >>>点赞 | 收藏 <<<