Article summary
We’ve recently been using a combination of TypeScript, GraphQL, and React on a number of single-page app projects at Atomic. These technologies works extremely well together, but only after a significant investment in configuration, tooling, and ergonomics.
After working on a couple of projects involving these technologies, we’ve moved to simplify the setup of GraphQL/React apps in TypeScript with a new starter kit.
Out of the box, this starter kit includes:
- A client based on React, Redux, and Redux-Saga
- An Express server hosting a GraphQL endpoint, configured as a 12-factor app to run in Heroku
- webpack configurations configured with source maps, minification in production, and source map support
- npm scripts for hot-reloading dev server, building, database migrations, tests, etc.
- Postgres connectivity with Knex, tooling to run Postgres in a Docker container, and DataLoader for server side batching
- Jest for unit testing
- Codecept via Nightmare.js for acceptance testing
- Bourbon, Neat, and Bitters for our CSS foundation
It has everything we expect out of a framework for building single-page web apps backed by an API. These capabilities tick all of the requirements boxes for building the kind of sophisticated web app we often build at Atomic, but I’m particularly excited about the details.
Interesting Properties
Automatic Type Generation
We’re automatically generating TypeScript types for both our GraphQL schema and our client queries and mutations. This gives us strong checking of our GraphQL resolver implementations as well as results of GraphQL queries. A change to a .graphql
file leads to immediate TypeScript errors when a query or schema is changed in a way that is incompatible with related code.
Unified App Architecture
Our small teams with uniform responsibility across our code bases prefer monorepos. We’ve structured our starter kit accordingly to take advantage of one single, statically typed language across both client and server.
Instead of structuring an app as a separate client and server, this starter kit is structured as one single application with multiple entry points, organized into modules representing individual concerns. The SPA client has one entry point, the Express server another. Projects may add additional entry points for other targets, such as background processes, AWS Lambda functions, or CouchDB view definitions.
Structured in this way, code sharing across build targets is extremely easy. This is helpful for sharing generic utilities, and it is extremely powerful when you put it together with TypeScript, which enforces consistency across the entire codebase. When combined with our type generation for GraphQL schema/queries, we get static checking of even client/server communication type compatibility.
Lenses
There is one challenge when combining TypeScript with Redux. Many common solutions to the problem of updating immutable data structures in the JavaScript ecosystem are fundamentally impossible to type via TypeScript–see Immutability Helper, for example. Redux’s built-in combineReducers
has similar issues.
Statically typed functional languages like Haskell and Scala solved this problem ages ago with lenses–functional getter/setter pairs that are simple, composable, and statically typed. We include a lens library by default.
Property-based Testing
In addition to using Jest for unit testing, we’ve included JSVerify for property-based testing.
Docker-based Service Dependencies
We include a docker-compose.yml
file for declaring and running database dependencies. No need to install PostgreSQL or other dependencies on your system and deal with version incompatibilities across projects.
Remaining Work
This starter kit is brand new, extracted from a few ongoing projects and updated from there. We’ve used it on a few small projects with success.
In the near future, we hope to:
- Build out-of-the-box support for isomorphic apps and other server-side rendering use cases
- Further improve the webpack configuration, in particular code splitting and content hashing
- Extract the lens library into a stand-alone package
- Improve database test integration
While this starter kit is new and changing rapidly, it already represents a huge leap in ergonomics, convenience, and fun versus other SPA tech stacks we’ve used in the past.