Trying t3-app for a project and my learnings

Been in the industry for over a couple of decades and having worked in Microsoft stack for the majority of it, I had a chance to try the t3-app for a project, it is pleasant to work with. It was a intuitive to add or modify features due to integration of the backend and the frontend using tRPC, and it provided a smooth development experience for to and fro communication.

I believe it would be one of my goto technology stack to start of any proof of concepts going forward along with Python and Django, and to do initial deployments unless huge scalability and need for high volume of background processing would be a requirement; which for majority of projects even on production would not be an immediate requirement or can be handled by scaling the infrastructure.

I recommend anyone who are doing hands-on development for communicating their ideas to stakeholders, teams or to get the initial setup friction-free to give t3-app a try.

That being said, since I didn’t have extended experience in Javascript / Typescript / Node land; I faced few hurdles along the way and solved them with the help of the community. The below are few stumbling blocks I had and the solutions for them.

Lint validation difference when developing vs building

A lot of linting errors get passed over when you are developing, or even when you run next lint but fail when you try building for the production deployment. The best workaround is to run both Typescript compilation and lint checking before moving to the production build by combining tsc --noemit and next lint.

Reference: Thanks to zolalsl for the solution at Typescript inconsistency between “next lint” and “next build”

Customizing Next-Auth

Added a custom property named role to enable role based authorization. The document at t3-app documentation was helpful for this.

Accessing objects using bracket notation in Typescript

Next.js is nice platform, but one of its advantage of creating routing from file structure means for role based navigation links you need to build your list of links and manage them.

The approach I took involved setting up a route json structure with role specific keys, then doing server side authorization on pages to make sure the current user had the appropriate role to access them.

The route json structure looked like

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
const commonRoutes = [
   {
      "name": "My Profile",
      "href": "/user/my-profile",
      "key": "myprofile",
      "subKey": "_root_",
      "showInNavbar": true
   }
]

const roleRoutes = [
   {
      "Role1": [
         ...commonRoutes,
         {
            "name": "Role 1 Modules",
            "key": "role1mods",
            "subKey": "_root_",
            "children": [...],
            "showInNavbar": true
         }
      ],
      "Role2": [
         ...commonRoutes,
         {
            "name": "Role 2 Modules",
            "key": "role2mods",
            "subKey": "_root_",
            "children": [...],
            "showInNavbar": true
         }
      ]
   }
]

Based on the currently logged in user’s role, I wanted to access their menu structure to populate the navigation bar. I was trying to do this by accessing the menu key using bracket notation as we do in Javascript.

1
2
3
// validate session and other objects before this...
const role = session.data.user.role as string;
const menuItems = Constants.roleRoutes[role];

But Typescript wouldn’t have any of that and complained

1
No index signature with a parameter of type 'string' was found on type '{ Role1: {name:string; path:string; key:string;}} ...

The solution was to use as keyof typeof and then use it as the bracket notation

1
2
3
// validate session and other objects before this...
const role = session.data.user.role  as keyof typeof Constants.roleRoutes;
const menuItems = Constants.roleRoutes[role];

Database migration deployment and seeding as part of CI/CD

t3-app provides a Dockerfile to help us start off the dockerizing process. The file consists of 3 stages; dependencies, builder and runner. I think the best stage to run the deployment and seeding would be at the builder stage, which is accomplished by appending SKIP_ENV_VALIDATION=1 yarn build; with SKIP_ENV_VALIDATION=1 yarn build && yarn prisma migrate deploy && yarn prisma db seed;.

Make sure your seeding script is re-runnable by using upsert and skipDuplicates: true option for createMany.

I also changed the images to be from node:18.13-bullseye-slim instead of node:16-alpine3.17 after I had issues with the openssl packages on alpine.

Finally I changed the hardcoded PORT environment variable to be an ARG, so that the images can be deployed on cloud solutions which provides the appropriate port number your application needs to listen during runtime.

The seeding instructions were followed from prisma’s documentation and cross-checked with t3-app’s documentation. When in the seeding file if you want to access the environment variables, use process.env instead of the env from server.mjs.

Handling file upload

tRPC is text based protocol, which means we cannot push binary data over the wire. We can either base64 encode the file and use tRPC or better to setup Next.js api endpoint for handling file upload alone. This will depend on what size of file uploads you are accepting and what justifies your efforts.


That’s mostly the things I wanted to keep a record to refer back when starting a new project on the same stack. Hope you all enjoy using t3-app as me and bring your ideas to life with minimal effort 👍🏽.

comments powered by Disqus