People occasionally ask: which technologies do you use? While we don’t believe the chosen technologies are as important as getting the requirements right, having the right tools can save a lot of development time. Here’s our preferred web tech stack, and a few alternatives for when they’re necessary.
When we evaluate a new technology, we assess three things (in order of priority):
- Support & Adoption: We only use well-adopted software with good support now, and in future. This makes us confident that the resulting software will be easy to maintain later on.
- Capability: Does a new technology offer things which weren’t possible or feasible before?
- Developer Speed: We want to spend development time effectively, so being willing to adopt new technologies can help if they have meaningful improvements
React gives us a language to describe the visual part of our applications in a simple declarative style. It’s well-suited to modern website design and is continuously updated.
TypeScript and React can produce quite verbose code when used together, but we find it preferable to other React typing solutions, such as the runtime type checker PropTypes.
Highlight: Its wide adoption makes it ideal for contract projects – we are confident that the system we deliver can be easily maintained in future even if we’re not around.
We love Material-UI! Where libraries like Bootstrap offer a general CSS framework, Material-UI provides fully-featured React components that follow Google’s Material Design guidelines. This offers a consistent effort-free base for projects where usability and functionality are more important than unique design. It helps that it looks fantastic too.
Highlight: Frequent updates keep it inline with Google’s standards
For data-heavy applications or ones requiring a substantial analysis platform, we may opt for an ASP.NET platform. Writing intensive analytical code is easier in a strongly typed language like C#, and relying on analytical packages available for .NET available makes development quick.
We like ORM layers like Sequelize or EF Core to provide a layer of abstraction between the database and our code. We try and keep our database interaction simple, so ORMs are well suited to handling the types of queries we need to write. It’s nice to be able to rely on the ORM for the boilerplate.
Highlights: Both Sequelize and EF Core offer an ‘escape hatch’: custom SQL can be written if required. We rarely have to use it!
SQL Database Technology?
We don’t mind! When given full control we will generally use MariaDB, but with the abstraction provided by Sequelize or EF Core the actual database becomes less important.
We’re not sold on whether it’s faster to write code with TypeScript, but we know it’s faster to maintain and build upon.
Highlight: We love the
undefined mechanic. When data has to be fetched, it’s inherently uncertain whether it’s available at time of render. By using
undefined sensibly it’s clear whether or not you need to check if data exists. Keeps ‘if-null’ statements to a minimum.
GraphQL vs REST for server communication is a big question in the development community. Old respected standard, or the new hotness from Facebook? In general we prefer the approach that is most established and understood. Except in this case – GraphQL offers so much that it’s hard to ignore. It also helps that there is a new and well-supported foundation to accelerate its adoption.
We moved to GraphQL to give us more control over what data we downloaded, but we stayed for the extra features. Where we once used Redux to provide caching and download metadata (loading, error handling), GraphQL provides this for free. It even handles refetching data when associated queries might have modified it.
Highlight: Gets around the constant tension in REST between keeping routes focused (but requiring a lot of extra fetch calls to collect related data) and providing a lot of related data every time (multiplying download sizes).
End-to-end testing can go a long way to instilling confidence in your software. However, testing complex web applications can be costly, and may get in the way of releasing frequent updates. Cypress is a significant improvement to the existing (painful!) Selenium-based technologies, which offer full automated testing, but are difficult to write and debug.
Highlight: Cypress’s customised browser platform makes testing simple, with tools for writing tests as well as running them.
When it comes to important business logic, end-to-end testing isn’t enough to capture all the different ways it might be used. We’ve found unit testing with Jest to be quick and easy, and a great way to ensure our software handles those unexpected edge cases. Additionally, we can incorporate Enzyme if we want to test specific React components.
When we write applications that live in the cloud we use Auth0 to provide security. We’re strong believers that you should leave security to security professionals, so we’re happy to be able to offload user management to them.
When writing applications that run inside a local network, we don’t use Auth0. Where possible we’d like applications running locally to not rely on an external internet connection, particularly for clients where this can be unreliable. In those cases we use standard JWT technology to ensure the data remains safe. The lowered risk of being inside the network reduces the need for an external provider like Auth0.
Highlight: Auth0 offers a comprehensive user management platform, making it cheaper for clients than having a dedicated one written. However, the generic nature of the platform does make it more complex to manage than a custom solution.
In future, we’d like to replace Sequelize with Prisma – an ORM that makes working with GraphQL easy. By generating a lot of type information directly from the database schema (and by automatically implementing a wider variety of queries over the database), a lot of the repetition and boilerplate goes away.
Prisma is still growing, but the next time we start a project we’ll be looking carefully at whether it’s ready for our use.
Highlight: The auto-generated filter and pagination routes for accessing data give a lot of flexibility for writing APIs without writing a lot of boilerplate code. Also, it works well with TypeScript!