T3 Stackを触ってみる
開発環境
- next 13.0.2
- react 18.2.0
- next-auth 4.15.1
- prisma 4.5.0
- trpc 10.0.0
前提
主にtRPCの実装例を交えた記事になります。
本題
T3 Stackとは
以下の思想に基づいて設計されたアプリケーションのdevelopment stackとして提唱されたものです。
- simplicity(簡潔さ)
- modularity(再結合性)
- full-stack typesafety(フルスタックな型安全性)
また、以下6つの技術を採用します。
- Next.js
- tRPC
- Tailwind CSS
- TypeScript
- Prisma
- NextAuth.js
フルスタックとあるように、バックエンドの実装はNext.jsのデフォルトで用意されているAPIルートを利用するため、プロジェクトとしてはNext.jsで完結する形になります。
また、tRPCはクライアント、サーバーとの通信をTypeScriptで型安全に開発を進められるREST APIやGraphQLの代わりになるものです。
tRPCの大きな特徴としては、サーバーサイドで実装してAPIの関数の引数や戻り値がクライアントサイドに自動的に共有される点で、これが型安全に開発が進められるとよく言われているポイントだと思います。
型を生成する必要がなく、GraphQLを利用するときに必要になるschemaの定義、管理なども必要なくなるのは大きなメリットだと思います。
そして、tRPCの内部では@tanstack/react-queryが使用されているので、クライアント側ではキャッシュの管理も行えます。
tRPCの注意する点としてはTypeScriptでのみ利用できるという点です。
バックエンドをその他Goなどの言語で利用することはできないので、その場合はREST APIやGraphQLを検討する必要がありますし、クライアントとサーバーサイドを完全に切り離した大規模な開発などでは良さを活かせないケースもあるかなと思います。
プロジェクト立ち上げ
$ npx create-t3-app@latest
コマンド実行後、対話式で入力が必要になります。
? What will your project be called? (my-t3-app) my-t3-app
? Will you be using JavaScript or TypeScript? TypeScript
Good choice! Using TypeScript!
? Which packages would you like to enable? nextAuth, prisma, tailwind, trpc
? Initialize a new git repository? (Y/n) No
Sounds good! You can come back and run git init later.
? Would you like us to run npm install? (Y/n) Yes
Alright. We'll install the dependencies for you!
? Which packages would you like to enable?
この質問で使用するライブラリを選ぶ必要があるので、すべて使用するならすべて選択します。
NextAuth.js
next-authが提供している signIn, signOut, useSession を利用することでシンプルに認証機能を実装できます。
Google, Githubなどと簡単に連携することができます。
また、以下の認証をサポートしているので、幅広く採用されるライブラリです。
- OAuth認証
- JWT認証
- データベースセッション認証
T3 StackのデフォルトではDiscordが利用されていますが、ここの実装部分を置き換えたり、追加していくことになります。
tRPC
【サーバー】
認証、非認証のプロシージャがsrc/trpc/trpc.ts内ですでに用意されているので、簡単なものであればそれらを利用しつつ ①型の定義②CRUDなどのロジック を実装するだけです。
例えば、タスクの作成、一覧取得であれば以下のようになります。
import z from "zod";
export const createTaskSchema = z.object({
title: z.string().max(20),
body: z.string().max(5),
});
export type createTaskInput = z.TypeOf<typeof createTaskSchema>;
import { createTaskSchema } from "../../../schema/task";
import { router, protectedProcedure, publicProcedure } from "../trpc";
export const taskRouter = router({
createTask: protectedProcedure
.input(createTaskSchema)
.mutation(async ({ ctx, input }) => {
return await ctx.prisma.task.create({
data: {
...input,
user: {
connect: {
id: ctx.session.user.id,
},
},
},
});
}),
getTasks: publicProcedure.query(({ ctx }) => {
return ctx.prisma.task.findMany({
where: {
userId: ctx.session?.user?.id,
},
orderBy: {
createdAt: "desc",
},
});
}),
});
最後にappRouterに登録することでクライアント側から利用できるようにします。
import { router } from "../trpc";
import { taskRouter } from "./task";
export const appRouter = router({ task: taskRouter });
// export type definition of API
export type AppRouter = typeof appRouter;
【クライアント】
サーバーの実装に合わせて、タスクを新規作成するカスタムフックを実装します。
import { trpc } from "../utils/trpc";
export const useMutateTask = () => {
const utils = trpc.useContext();
const createTaskMutation = trpc.task.createTask.useMutation({
onSuccess: (res) => {
const previousTasks = utils.task.getTasks.getData();
if (previousTodos) {
utils.todo.getTasks.setData(undefined, [res, ...previousTodos]);
}
reset();
},
});
return { createTaskMutation };
};
キャッシュデータを参照するためにtrpc.useContext()でutilsオブジェクトを作成しています。
また、tRPCはreact-queryを内包しているため、onSuccessを同じように定義していきます。
普段react-queryを使い慣れている人からすると違和感を感じますが、キャッシュデータへのアクセスに一意のkeyを指定する必要がなく、メソッドチェーンを解釈して特定しています。
utils.task.getTasks.getData()
最後に
シンプルなアプリケーションをさくっと作りたい!
フルスタックにTypeScriptで作りたい!
というときに選択肢の一つになるかなと思います。
特に、フルスタックにTypeScriptで実装する選択肢は現状、
- Express(TypeScript)
- NestJS
- T3 Stack
なので、NestJSを触っていない方からすると重宝するのではないでしょうか。