Modtree's tech stack
For context, here's the list of key technologies that Modtree uses, listed from the most abstract to the most visually concrete.
TypeScript · PostgreSQL · TypeORM · Webpack · Jest · Express · tRPC · GitHub Actions · Redux · React · Next · React Flow · Tailwind CSS · NextAuth.js
Building
TypeScript
TypeScript is the programming language of Modtree. We find that types help to communicate a large part of what each function and data structure is trying to accomplish.
Apart from having static types, it is also useful that the entire tech stack can be written in this language, so sharing code is highly feasible.
Webpack
Webpack lets us bundle a large amount of code into just one executable JavaScript file, allowing for a minimal production runtime file.
GitHub Actions
Modtree's CI/CD runner, which automatically tests code on every push, and deploys our services on every merge to the main branch.
Database
PostgreSQL
Postgres is our one and only database type (for now). We tried MySQL but ditched it because of the simple pragmatic reason that we couldn't find free online hosts which offer a reasonable amount of storage space.
In the longer term, we'd like to explore graph databases to handle our module dependency relations. Graph relation functions written in SQL will likely never be as natural as those written in GraphQL, so extending such functions will probably be easier with GraphQL.
TypeORM
TypeORM is a object-relational mapping (ORM) tool. It provides us with an object-oriented abstraction for the database, facilitating development.
TypeORM also has the flexibility to allow us to write SQL queries directly in TypeScript, and also plays nice with TypeScript’s types and interfaces.
Backend
Express
Our server functions and API endpoints are handled by Express. Express provides us with route matching and parsing capabilities so we don’t have to worrying about implementing it.
tRPC
tRPC helps us to statically type APIs. We are thus able to share types between our client (frontend) and server, so that we are confident in using our API correctly. Given that our project’s main language is TypeScript, this is a very useful addition to our application.
NextAuth.js
With NextAuth.js, users can now authenticate using social logins. We currently support Google, Github, and Facebook.
Since our target audience is NUS undergraduates or incoming undergraduates, they are likely to own one of the abovementioned social accounts. By having social logins, we reduce the barrier of entry for using our app, encouraging users to sign up as it is hassle-free.
Frontend
React
This is the core language that all our frontend is based on. Specifically, we write with the intended style of React 17, with hooks instead of classes. So instead of having classes that extend a base React Component, our frontend components are Functional Components whose states are managed with hooks such as useState and useEffect.
Next.js
Of course, we had the choice to use pure React.js, but we opted to use it on top of Next.js because of its extra routing capabilities. Through these routes we use their API functionality to manage authentication and centralize our backend calls.
React Flow
This is the library that is responsible for displaying nodes and edges that represent modules and their requirements. React Flow is an open-source tool which lets us skip past the issue of implementing togglable, draggable, (multi)selectable nodes, and get right into the core business of working on module dependency logic.
Redux
Redux is a core part of our frontend. It centralizes our application’s state and abstracts the logic away from frontend components. This helps us to get a single source of truth for the global state of our application, so that any changes a user makes is correctly updated throughout our application.
Tailwind CSS
Tailwind CSS allows us to write CSS directly in JSX, a major convenience when we are building fresh components and are in the experimental stage. Though this breaks the philosophy of the cascade, it provides us a much needed speed boost when doing up proof-of-concepts. It also still lets us use vanilla CSS, which we do move over to for more mature components.
Testing
Jest
We write our tests in TypeScript, using Jest as our testing framework.
At Milestone 2, we spin up a database instance whenever we run unit tests for our core functionality. Since we also wanted to keep our code coverage high, this made our unit tests run for longer than 2 minutes, both locally and in the CI.
The bottleneck of the unit tests is spinning up the database instance. Hence, we decided to introduce mocking to eliminate such external dependencies from unit testing. As a result, our unit tests run in 10 seconds now.
Cypress
We use Cypress for end-to-end testing. Cypress spawns a browser and helps us to simulate user actions such as clicking and typing into a text box, effectively simulating a real user. We are also able to assert that certain elements are loaded in the page. Overall, this helps us to confirm that our core business logic works.