Skip to content

My experience building a community bingo using the T3 Stack

Posted on:January 2, 2023 at 03:22 PM

For the german Youtube series “7 vs. Wild” I wanted to build a community bingo at Before the series started, the community was asked to submit their ideas for the bingo cards and were able to create their own bingo cards using the submitted ideas. Each participant earned points based on the number of bingos and difficulty of the choosen Bingo card. You can check out the final result here and the winner board here.

In total the site had:

Table of contents

Open Table of contents

T3 Stack

The main website was built using the T3 Stack. The t3 stack was popularized by Theo and typesafe way to build fullstack web-applications

The stack uses the following technologies:

Using the t3 stack was an absolut blast. Thanks to tRPC building API endpoints was easy. To create a new endpoint I only had to follow the already generated structure form the create-t3-app and add a new entry to the router. Then I could simply query the database from the frontend:

//example router used to query the top 10 players with a bingo board
export const pointsRouter = createRouter()
  .query("highscore", {
    async resolve({ ctx }) {
      return await ctx.prisma.score.findMany({
        orderBy: {
          score: "desc",
        take: 10,
//frontend code to query the code
const Board: React.FC = () => {
  const { data: entries } = trpc.useQuery(["highscore"]);
  return (
      <div className="w-48 text-sm font-medium  rounded-lg border  bg-gray-700 border-gray-600 text-white">
          className="py-2 px-4 w-full font-medium text-left text-white  rounded-t-lg border-b cursor-pointer focus:outline-none bg-gray-800 border-gray-600"
        {entries?.map((entry, index) => (
            className="block py-2 px-4 w-full rounded-b-lg cursor-pointer  focus:outline-none focus:ring-2  border-gray-600 hover:bg-gray-600 hover:text-white focus:ring-gray-500 focus:text-white"
            {index + 1}. {entry.userName} - {entry.score}

export default Board;

Also using prisma to query the database was a breeze. I only had to create a new model and add the fields I wanted to store in the database. Then I could use the generated prisma client to query the database completely type safe. For example this is the model used for the player score table seen above:

model Score {
    id        String   @id @default(cuid())
    userId    String   @unique
    user      User     @relation(fields: [userId], references: [id], onDelete: Cascade)
    userName  String
    score     Int      @default(0)
    position  Int      @default(0)
    createdAt DateTime @default(now())
    updatedAt DateTime @updatedAt

tRPC directly integrates react-query which is awesome because it makes caching and invalidation of data very easy. Also you get a lot of nice features like pagination, optimistic updates and more out of the box. The application feels a lot more responsive because of this. For the /points and /cards page I used the getStaticProps function from Nextjs to pre-render the pages. This way the page loads instantly and the data is always up to date.

I also used next-auth to handle the authentication. This provided example generated by the create-t3-app was a great starting point. I only had to add the discord and twitch authentication. Example of the authentication



Instead of using vercel to host the application I decided to use a self hosted server and try out coolify. Coolify is an open-source & self-hostable Heroku / Netlify alternative. It connects to your git repository and automatically deploys your application. It also takes care of the SSL certificates and provides a nice dashboard to manage your applications. Coolify Dashboard


I always wanted to try planet scale and this was the perfect opportunity. Planet scale is a database as a service that provides a mysql compatible database and advertises a lot of nice features like automatic failover, replication and more. It manages the database simlar like a git repository. You can create branches of table changes and merge them to add the changes to the main branch. This makes it easy to test changes without breaking the main branches and interrupting the service.

In my experience the database was very fast and in combination with prisma the pages feel really snappy. My use case was not very demanding but I would definitely use it again for a bigger project. Sadly planet scale had one outage on my replication server which caused the website to be down for a few hours. After reaching out to the support the issue was resolved quickly.

I started with the free plan on planet scale and upgraded to the paid plan after my usage crossed the 1 billion rows read limit. Planet scale dashboard. I didn’t implement any caching in the t3 stack which would have helped a lot with the read limit. When viewing the /points or /board page the application would query the database for every single player. This is not very efficiently and if I would do it again I would implement some caching to reduce read operation.


I used cloudflare to manage the DNS and provide basic DDOS protection. Cloudflare also provided basic caching of static assets. cloudflare dashboard


For analitics i used plausible. Plausible is a privacy friendly analytics tool that does not track users. It only tracks page views and does not use cookies. I self-hosted plausible on my local k3s cluster.

Most of the page views correlated to the release of new episodes of 7 vs. Wild. In the first week we did some promotion on twitter and reached a lot of users: Plausible analytics


I really loved working with the t3 stack. The template provides a great starting point. Next time I would definitely implement some caching to reduce the read operation of the database. Nextjs 13 provides a lot of awesome new features to make these kind of operations a lot easier and i can’t wait to see how the t3 stack will evolve in the future to take advantage of these new features. Also using planet-scale was a great experience, but maybe a bit overkill for my use case. Next time would probably stick to using sqlite which is plenty fast for most use cases.