今天我們要來實作 refetch_token
跟 access_token
實作~流程大概會是如下。
> npx create-next-app@latest authorization --ts
這邊使 trpc
團隊推薦的 tsconfig
,筆者可以根據自己偏好調整
//tsconfig.json
{
"compilerOptions": {
"target": "es6",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"strictNullChecks": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"noUncheckedIndexedAccess": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"baseUrl": ".",
"paths": {
"~/*": ["src/*"]
}
},
"include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx",
"./*.js",
"./src/**/*.js"
],
"exclude": ["node_modules"]
}
這邊我們的 db
會用 docker
起一個 postgresQL
跟 redis
。
// docker-compose.yml
version: '3'
services:
postgres:
image: postgres
container_name: postgres
ports:
- '6500:5432'
restart: always
env_file:
- ./.env
volumes:
- postgres-db:/var/lib/postgresql/data
redis:
image: redis:latest
container_name: redis
ports:
- '6379:6379'
volumes:
- redis:/data
volumes:
postgres-db:
redis:
這邊是本次專案會用到的 env
,以下是範例讀者也可以自行調整。
// .env
DATABASE_PORT=6500
POSTGRES_PASSWORD=password123
POSTGRES_USER=postgres
POSTGRES_DB=trpc-nextjs
POSTGRES_HOST=postgres
POSTGRES_HOSTNAME=127.0.0.1
NEXT_PUBLIC_TRPC_ENDPOINT=""
ACCESS_TOKEN_PRIVATE_KEY=""
ACCESS_TOKEN_PUBLIC_KEY=""
REFRESH_TOKEN_PRIVATE_KEY=""
REFRESH_TOKEN_PUBLIC_KEY=""
接著我們 docker
啟動~
> docker-compose up -d
然後檢查 port
有沒有成功對接。
> docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
4c9ab62e6c53 postgres "docker-entrypoint.s…" 24 hours ago Up 2 minutes 0.0.0.0:6500->5432/tcp postgres
6544419c75b1 redis:latest "docker-entrypoint.s…" 2 days ago Exited (255) 2 minutes ago 0.0.0.0:6379->6379/tcp redis
> npm install @prisma/client && npm install -D prisma
初始化 prisma
> npx prisma init
然後記得修改一下 DATABASE_URL
的值
// .env
DATABASE_PORT=6500
POSTGRES_PASSWORD=password123
POSTGRES_USER=postgres
POSTGRES_DB=trpc-nextjs
POSTGRES_HOST=postgres
POSTGRES_HOSTNAME=127.0.0.1
DATABASE_URL="postgresql://postgres:password123@localhost:6500/trpc-nexjs?schema=public"
新增 schema
,這邊順便補充一下用到的 decorator
好了。
@@map
: 指定 orm
映射到 db
的 table
名稱叫什麼, @@map("users")
代表 User modal
會在 db
中,對應到 users
的 table
。@db.VarChar
: prisma
有提供一些 db
的 validate
功能例如 VarChar
設定。enum
: prisma
可以透過 enum type
列舉出有哪些 value
可以填入。
model User{
@@map("users")
id String @id @default(uuid())
name String @db.VarChar(255)
email String @unique
photo String @default("default.png")
verified Boolean @default(false)
hashPassword String?
role RoleEnumType @default(user)
createAt DateTime @default(now())
updateAt DateTime @updatedAt
provider String?
}
enum RoleEnumType {
user
admin
}
之後我們寫 script
來 migrate
// package.json
{
"scripts": {
"db:migrate": "npx prisma migrate dev --name user-entity --create-only && yarn prisma generate",
"db:push": "npx prisma db push"
},
}
--create-only
: 先產生 migrate schema
但還沒應用到 db
,可用於檢視 SQL command
有無錯誤。
>npx prisma migrate dev --create-only
之後再跑一次 npx prisma migrate dev
db
就會吃 migrate
的內容了~
>npx prisma migrate dev
之後只要每次到 npm run
就會幫你 migrate schema
然後產生 prisma client
的 type
了。
> npm run db:migrate && npm run db:push
透過 docker
有 redis
的 local
環境後,就需要加 client instance
拉~
// src/server/utils/connectRedis.ts
import { createClient } from 'redis'
const redisUrl = `redis://localhost:6379`;
export const redisClient = createClient({
url: redisUrl
})
const connectRedis = async () => {
try {
console.log('? Redis client connected...');
await redisClient.connect()
redisClient.set(
'trpc',
'welcome to trpc'
)
} catch (e: any) {
await redisClient.disconnect()
console.log(e.message)
process.exit(1)
}
}
connectRedis()
redisClient.on('error', (err) => console.error(err))
import { PrismaClient } from "@prisma/client";
declare global {
var prisma: PrismaClient | undefined
}
export const prisma = global.prisma || new PrismaClient()
if (process.env.NODE_ENV == 'development') global.prisma = prisma
export const connectDB = async () => {
try {
await prisma.$connect()
console.log('? Database connected successfully');
} catch (e: any) {
console.error(e.message)
await prisma.$disconnect()
process.exit(1)
} finally {
await prisma.$disconnect()
}
}
> npm install @trpc/server@next @trpc/next@next superjson redis
@trpc/next
: 這是 trpc server
管理 procedures
跟 router
的套件。@trpc/next
: 讓 trpc server
可以用在 nextjs
的 api handler
。superjson
: 序列化與反序列化的套件,方便解析 Date
或是 new Set
等等的資料。redis
: redis server
。
這邊我們簡單寫一個 createContext function
日後可以用到 createNextApiHandler
中, 並使用 inferAsyncReturnType
去自動推論 Context type
是什麼。
//src/server/createContext.ts
import { inferAsyncReturnType } from "@trpc/server";
import { NextApiRequest, NextApiResponse } from "next";
export function createContext({
req,
res,
}: {
req: NextApiRequest;
res: NextApiResponse;
}) {
return { req, res };
}
export type Context = inferAsyncReturnType<typeof createContext>;
這邊我們用 superjson
幫我們做 transformer
,好處就是 transformer
會自動序列化 Date
甚至是 new Set
的資料,這樣就不需要額外處理了。
// src/server/createRouter.ts
import { initTRPC } from "@trpc/server";
import superjson from "superjson";
import { Context } from "./createContext";
export const t = initTRPC.context<Context>().create({
transformer: superjson,
});
export const publicProcedure = t.procedure
export const router = t.router
這邊簡單測試 redisClient
功能~
import { t } from "../createRouter";
import redisClient from "../utils/connectRedis";
export const appRouter = t.router({
getHello: t.procedure.query(async ({ ctx }) => {
const message = await redisClient.get("trpc");
return { message };
}),
});
export type AppRouter = typeof appRouter;
這邊別放忘記把 createContext
放進去喔~
import * as trpcNext from "@trpc/server/adapters/next";
import { appRouter } from "~/server/routers/app.routes";
import { createContext } from "~/server/createContext";
export default trpcNext.createNextApiHandler({
router: appRouter,
createContext,
});
測試一下 api
~
> npm install @trpc/react-query@next @trpc/client@next @tanstack/react-query @tanstack/react-query-devtools
這邊特定設定 staleTime
5 秒,如此 5 秒內的 request
永遠都是 refresh
的。
import { AppRouter } from '../server/routers/app.routes';
import { httpBatchLink } from '@trpc/client';
import { createTRPCNext } from '@trpc/next';
import SuperJSON from 'superjson';
function getBaseUrl() {
if (typeof window !== 'undefined')
// browser should use relative path
return '';
if (process.env.VERCEL_URL)
// reference for vercel.com
return `https://${process.env.VERCEL_URL}`;
if (process.env.RENDER_INTERNAL_HOSTNAME)
// reference for render.com
return `http://${process.env.RENDER_INTERNAL_HOSTNAME}:${process.env.PORT}`;
// assume localhost
return `http://localhost:${process.env.PORT ?? 3000}`;
}
export const api = createTRPCNext<AppRouter>({
config(opts) {
return {
transformer: SuperJSON,
links: [
httpBatchLink({
url: `${getBaseUrl()}/api/trpc`,
// You can pass any HTTP headers you wish here
async headers() {
return {
};
},
}),
],
queryClientConfig: {
defaultOptions: {
queries: {
staleTime: 5 * 1000
}
}
}
};
},
ssr: false,
});
之後記得加 ReactQueryDevtools
到 _app.tsx
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
import type { AppType } from 'next/app';
import { api } from '~/utils/trpc';
const MyApp: AppType = ({ Component, pageProps }) => {
return (
<>
<Component {...pageProps} />
<ReactQueryDevtools initialIsOpen={false} />
</>
);
};
export default api.withTRPC(MyApp);
https://github.com/Danny101201/refetch-token/tree/main
✅ 前端社群 :
https://lihi3.cc/kBe0Y