Our team invests considerable time in validating ideas and constructing MVPs. Therefore, establishing a robust tech stack is crucial for us. With the frequent initiation of new projects, we've had plenty of time to test various technologies in real-life scenarios, compare them, and select the ones that prove most effective for our needs.
In this article, we'll closely examine the stack our team uses to build MVPs and products for startups.
Here are the key criteria we prioritize when selecting technologies for our stack:
Fast Development: The chosen stack should facilitate fast development of new features.
Ease of Maintenance: It should be seamless to maintain existing features, ensuring long-term sustainability.
Scalability: The tech stack should easily scale to accommodate a growing user base.
Active Maintenance: Every framework and library should be actively maintained to avoid reliance on obsolete third-party technologies.
Recruitment Friendly: The stack should facilitate the recruitment of new team members with ease.
Comprehensive Documentation: A well-documented tech is essential for efficient development and troubleshooting processes.
To enhance clarity, we've assigned one of the following statuses to each technology:
✅ Keep Using - We have a history of using it and plan to continue.
❌ Stop Using - We've used it in existing projects but won't use it in new ones.
▶️ Start Adoption - We haven't used it before but plan to integrate it into new projects.
⏩ Keep Adoption - We recently started using it and will continue in new projects.
⏸️ Wait with Adoption - We like the technology but believe it's too early to incorporate into our workflow.
This year marked significant growth in AI tooling, with our team exploring various new tools and products entering the market.
We rely on ChatGPT for a wide range of tasks, from assisting in email composition to proofreading this article 😅.
The era when developers would default to using MongoDB without specific reasons, merely for its ease in prototyping, has passed. We have consistently avoided following trends and instead choose databases tailored to each project. In our experience, relational databases have often proven more effective for our needs.
As our primary database, we've used PostgreSQL in all our past projects, finding it well-suited for various setups.
We typically opt for hosting solutions that align with our clients' preferences. Many startups qualify for the Startup Programs offered by major cloud providers such as AWS, Google Cloud, Microsoft Azure, Oracle Cloud, etc. However, if a client is unwilling or unable to secure credits through these programs, we suggest a straightforward and user-friendly alternative that can be easily managed and understood, even by individuals without technical expertise.
Digital Ocean stands out as a simple and elegant hosting provider that caters to various clients. We've successfully hosted projects entirely on Digital Ocean, ranging from basic Staging environments with minimal resource consumption to self-scaling Production environments. This includes features such as database replication, regular backups, and other essentials for startups at every step of their early stage.
Managing payments is a crucial aspect of every SaaS platform, and ensuring a swift and straightforward method for online transactions is a priority for us.
We rely on Stripe to handle both individual payments and subscriptions. It offers support for various features, including coupon codes, invoice generation, and other functionalities essential for SaaS platforms.
Effective email communication is integral to any modern web application, and after experimenting with various solutions, we've settled on the choices we currently use.
Having explored multiple email template languages, we've found an optimal library for crafting email templates. This library allows us to build templates using React and seamlessly render them on the server in real-time, incorporating all the necessary data.
in the realm of sending emails, SendGrid has been our go-to solution for nearly every project. Its free plan is well-suited for MVP projects until they attract genuine paying users. SendGrid's user-friendly dashboard provides valuable insights into the statistics related to emails sent through the application.
While our backend is powered by NodeJS, the intricacies of coding in pure NodeJS can be challenging. Hence, we leverage frameworks built on top of NodeJS for a more efficient development process.
NestJS, an opinionated NodeJS framework, is exclusively built in TypeScript, offering excellent extensibility. This framework enables us to separate our backend into independent modules, significantly enhancing code maintainability. With comprehensive documentation and great learning materials, NestJS proves to be an excellent choice for onboarding new team members.
Prisma stands out with its TypeScript support, although it comes with its own set of considerations. Designing the data model in Prisma involves a single file, which can be challenging to maintain in larger projects. Additionally, generating all types requires the ORM to modify the node_modules directory, posing potential issues in some cases.
Drizzle ORM is a recently introduced TypeScript-based ORM boasting impressive features, ease of learning, and high-speed performance, as claimed by its authors. While we prefer testing new technologies in our internal small projects before adopting them for clients, Drizzle ORM is on our radar for future projects. Its promising features suggest it may address some of the issues we've encountered with Prisma.
When prioritizing speed over design in the development of an MVP, it's practical to use pre-designed component libraries. Here's where libraries like Material UI, Bootstrap, Radix, and others truly shine.
While Chakra UI stands out as a solid component library, it comes with its limitations. Not having an official Figma file poses a challenge in our workflows, where quick design visualization is crucial. Another notable drawback is Chakra UI's use of the css-in-js approach, potentially leading to performance issues, especially in large projects with Server-Side Rendering.
In 2022, our friend Tair Asimov recommended Radix, and after experimenting with it, we officially started adopting it in all our new projects in 2023. The experience has been positive, and Radix has proven to work seamlessly.
State management was the hardest topic in React for past years, but recently it changed in a very positive way. The mindset of the React developers has shifted towards separation of the Server and Client state, and libraries like React Query and SWR appeared.
One of the most revolutionary developments in the React ecosystem is React Query, fundamentally changing the way React code is written. It eliminates the need for boilerplate code, alleviates the complexities of state declarations seen in Redux, and removes concerns about when to refetch data from the server.
It's worth to mention that React Query has excellent documentation and a collection of tutorial videos created by the library maintainers.
For storing application state outside of React, we turn to either Zustand or Jotai, depending on the specific case. Both libraries, authored by the same individual, offer simplicity, thorough documentation, and effective maintenance.
Signals are increasingly being incorporated into various frameworks. In terms of developer experience, Signals offer notable advantages over hooks, making them a potential alternative to other state management libraries. Our friend, Ilya Zayats, recently shared a Signals implementation from the Preact Team. While we appreciate the direction this approach is taking, we're opting to wait until it gains more widespread usage before fully adopting it.
Handling forms is perhaps the second most challenging aspect after State Management. Initially, we endeavored to enhance our developer experience (DX) by creating our own solutions, leveraging state managers. However, we eventually realized that there are libraries outperforming our bespoke solutions.
In previous projects, we used Formik, and it served us well. However, we encountered some limitations. The primary reason for discontinuing its use is that it was constrained to specific validation libraries, notably lacking support for Zod.
Don't get us wrong; it's a commendable library that fulfills its purpose, but we discovered another library that better suits our use cases.
Various approaches exist for styling modern web applications, such as pure CSS, preprocessors like Sass, css-in-js libraries like styled components, and style frameworks like Bulma and Tailwind. Despite experimenting with all these options, we have settled on using CSS with postprocessing through PostCSS.
CSS Modules offer a cleaner way to write styles, eliminating the need for CSS nesting by confining the style scope to the component level. Each component can have its unique class names without requiring prefixes. This simplifies the stylesheets and significantly facilitates troubleshooting.
In our testing approach, we prioritize maintainability and a positive developer experience. As a result, we lean towards established technologies rather than extensive experimentation, ensuring a stable and efficient testing environment.
In our testing practices, we lean towards the traditional and proven approach, sticking with Jest, a tool that has stood the test of time.
During the initial stages of MVP development, we usually forego end-to-end testing. However, as projects expand, it becomes prudent to cover key workflows with such tests. Playwright, a relatively recent addition, addresses the shortcomings of previous end-to-end testing tools. We plan to explore it further when the need for end-to-end testing arises.
Covering every aspect of application development is challenging, and we may have missed something. If there are specific parts of our tech stack you'd like to know more about, please let us know on Twitter and LinkedIn. We're more than happy to share our knowledge with the community!