Criando rotas
O nextjs por padrão aceita que criemos a API usando a pasta server, nela podemos colocar nossas rotas e o nextjs irá entender que é uma rota da API.
Caso ainda tenha algum arquivo no caminho src/server/api/routers você pode remover, pois não iremos usar, no meu caso eu removi o arquivo post.ts.
O primeiro que vamos criar vai ser o nosso controlador de personagem, para isso vamos criar o arquivo character.ts na pasta src/server/api/routers.
Nossa estrutura base para praticamente todos os arquivos vai ser essa:
import { z } from "zod";
import {
createTRPCRouter,
protectedProcedure,
publicProcedure,
} from "~/server/api/trpc";
export const postRouter = createTRPCRouter({
// aqui vem todas as rotas que vamos interagir com o front-end via TRPC
});
Criando um personagem
A primeira funcão que vamos criar vai ser a de criar um personagem, então vamos criar uma mutation para isso.
Caso não esteja familiarizado com TRPC existem 2 tipos funções que podemos criar, as mutations e as queries, as mutations são funções que alteram o estado do servidor, como por exemplo criar um personagem, já as queries são funções que retornam dados, como por exemplo pegar todos os personagens.
O formato de uma query consiste em:
nome: publicProcedure ou protectedProcedure
.input(z.object({ input: z.string() })) <- Argumentos ou input da função
.query(({ input }) => { // aqui vem a função que vai ser executada
return {
// aqui vem o retorno da query
};
}),
PublicProcedure é uma função que pode ser acessada por qualquer um, já a protectedProcedure é uma função que só pode ser acessada por usuários logados.
Eu estou usando o Zod para validar os inputs mas você pode receber os inputs como um objeto e validar manualmente, mas o Zod é muito bom para isso.
Mole né?
Vamos criar agora as funções necessarias para manipular os personagens.
TRPC Router
GetChars
Nossa função getChars vai ser uma query, vamos retornar todos os personagens do usuário logado, vamos ordenar por level.
Create
Nossa funcão create vai ser uma mutation, vamos receber o nome e o Id da classe do personagem, vamos checar também se o usuário já tem 4 personagens, caso tenha vamos retornar um erro.
LevelUp
Nossa função levelUp vai ser uma mutation, vamos receber o id do personagem, vamos checar se o personagem existe e se ele pertence ao usuário logado, caso não exista ou não pertença ao usuário vamos retornar um erro, caso exista vamos aumentar o level do personagem em 1 e adicionar 5 pontos para ele distribuir.
Delete
Nossa função delete vai ser uma mutation, vamos receber o id do personagem, vamos checar se o personagem existe e se ele pertence ao usuário logado, caso não exista ou não pertença ao usuário vamos retornar um erro, caso exista vamos deletar o personagem.
character.ts
import { z } from "zod";
import { createTRPCRouter, protectedProcedure } from "~/server/api/trpc";
export const characterRouter = createTRPCRouter({
getChars: protectedProcedure.query(async ({ ctx }) => {
const chars = await ctx.db.character.findMany({
where: {
userId: ctx.session.user.id,
},
include: {
class: true,
},
});
return chars.sort((a, b) => a.level - b.level);
}),
create: protectedProcedure
.input(z.object({ name: z.string().min(1).max(20), classId: z.number() }))
.mutation(async ({ ctx, input }) => {
const userChars = await ctx.db.character.findMany({
where: {
userId: ctx.session.user.id,
},
});
if (userChars.length >= 4) {
throw new Error("User has reached the maximum number of characters.");
}
return ctx.db.character.create({
data: {
name: input.name,
class: {
connect: {
id: input.classId,
},
},
user: {
connect: {
id: ctx.session.user.id,
},
},
charStats: {
create: {},
},
inventory: {
create: {},
},
},
});
}),
levelUp: protectedProcedure
.input(z.object({ id: z.number() }))
.mutation(async ({ ctx, input }) => {
const char = await ctx.db.character.findFirst({
where: {
id: input.id,
userId: ctx.session.user.id,
},
});
if (!char) {
throw new Error("Character not found.");
}
return ctx.db.character.update({
where: {
id: input.id,
},
data: {
level: char.level + 1,
experience: 0,
points: (char.points || 0) + 5,
},
});
}),
delete: protectedProcedure
.input(z.object({ id: z.number() }))
.mutation(async ({ ctx, input }) => {
const char = await ctx.db.character.findFirst({
where: {
id: input.id,
userId: ctx.session.user.id,
},
});
if (!char) {
throw new Error("Character not found.");
}
return ctx.db.character.delete({
where: {
id: input.id,
},
});
}),
});
TRPC Provider
Para podermos acessar as funções do TRPC precisamos expor o arquivo que criamos dentro do router raiz, src/server/api/root.ts
.
Para isso basta importar o arquivo que criamos e adicionar ele no objeto de routers.
root.ts
import { createTRPCRouter } from "~/server/api/trpc";
import { characterRouter } from "./routers/character";
/**
* This is the primary router for your server.
*
* All routers added in /api/routers should be manually added here.
*/
export const appRouter = createTRPCRouter({
character: characterRouter,
});
// export type definition of API
export type AppRouter = typeof appRouter;