- Stack
-
- Next.js
- TypeScript
- Prisma
- tRPC
- Source code
- marekh19/cutest-mon
- View the app
- cutest-mon.marek.work
The Project
I wanted to learn more about connecting Next.js apps to databases, so I built upon Theo’s excellent tRPC tutorial. The result was “Cutest Pokémon” - a simple voting app where users choose between two randomly selected Pokémon. While heavily based on the tutorial, I made some modifications to make it my own learning experience.
What I Actually Built
Core Functionality
The app randomly selects two Pokémon from the Pokedex and lets users vote on which is cuter. Votes are stored in a simple database schema:
// schema.prisma
model Vote {
id String @id @default(cuid())
createdAt DateTime @default(now())
votedFor Pokemon @relation(name: "votesFor", fields: [votedForId], references: [id])
votedForId Int
votedAgainst Pokemon @relation(name: "votesAgainst", fields: [votedAgainstId], references: [id])
votedAgainstId Int
}
Random Selection Logic
Basic random number generation with duplicate prevention:
export const getOptionsForVote = () => {
const firstId = getRandomPokemon()
const secondId = getRandomPokemon(firstId)
return [first, second]
}
export const getRandomPokemon: (notThisOne?: number) => number = (notThisOne) => {
const pokedexNumber = Math.floor(Math.random() * MAX_POKEDEX_ID) + 1
if (pokedexNumber !== notThisOne) return pokedexNumber
return getRandomPokemon(notThisOne)
}tRPC API
Two simple endpoints - one to get a Pokémon pair, another to record votes:
export const appRouter = trpc
.router()
.query('get-pokemon-pair', {
async resolve() {
const [first, second] = getOptionsForVote()
const bothPokemon = await prisma.pokemon.findMany({
where: { id: { in: [first, second] } },
})
return { firstPokemon: bothPokemon[0], secondPokemon: bothPokemon[1] }
},
})
.mutation('cast-vote', {
input: z.object({
votedFor: z.number(),
votedAgainst: z.number(),
}),
async resolve({ input }) {
return await prisma.vote.create({
data: {
votedAgainstId: input.votedAgainst,
votedForId: input.votedFor,
},
})
},
})What I Did Differently from the Tutorial
- Styled Components: Used CSS-in-JS for styling (the tutorial used Tailwind)
- Project Structure: Organized code into features rather than by file type
Key Learning Outcomes
Prisma Basics
- Setting up database schemas and relationships
- Basic CRUD operations with the Prisma client
- Database migrations and seeding
tRPC Fundamentals
- Creating type-safe API endpoints
- Connecting frontend and backend with shared types
- Basic query and mutation patterns
Next.js Features
- API routes for backend functionality
- Static generation for the results page
- Basic routing between pages

The Reality Check
This was very much a beginner project. The code is straightforward, the features are basic, and there’s nothing particularly innovative here. But it served its purpose:
- Hands-on experience with modern tools I’d only read about
- Understanding the flow from database to frontend
- Building confidence in working with full-stack TypeScript
- Learning through modification rather than just copying
What I’d Do Differently Now
Looking back, I’d change quite a few things:
- I don’t use CSS-in-JS anymore - I don’t think it’s the right approach, but that’s a topic for another article
- Extract more logic into custom hooks instead of keeping it inside components
- Use a proper feature-sliced project structure
- Avoid the old pattern of naming everything
index.tsinside nested folders (I cringe looking back 😅) - Split components more thoughtfully
- Use constants instead of magic numbers/strings
- Dependencies are outdated - upgrading would cause too many breaking changes, so not worth it here
On a practical note, I recently had to switch the database provider to Turso to keep the app running on a free tier. It was initially on PlanetScale, but they killed their free tier a while back, so I resurrected this project with Turso just to make it usable again.
Conclusion
“Cutest Pokémon” is exactly what it sounds like - a learning project that helped me understand the basics of modern full-stack development. It’s not impressive code, but it represents an important step in my journey. Sometimes the best way to learn is to build something simple, make it work, and then understand why it works.
The project taught me that tools like Prisma and tRPC make full-stack development much more approachable than I initially thought, even if the end result is just a basic voting app.