Listifi tech stack

· erock's devlog

An overview of the tech choices I made for listifi.app

Given I recently launched listifi, I thought it would be interesting to talk briefly about the listifi tech stack.

Tech stack #

I'd be remiss if I didn't use my app before discussing my list of stack choices. Here is my tech stack.

In general, when I build an app I tend to focus on gluing the pieces together myself. This is my personal preference, but I find that frameworks or libraries that try to do too much end up getting in my way very quickly.

Provide some helper utilities and then get out-of-my-way.

The overall folder/file structure follows the patterns I described in my previous blog article.

Front-end #

There are some common choice in my list, in particular: typescript and react. I also opted to use a component library chakra-ui primarily because it made it easy to make CSS changes by using react component props. It also wasn't a massive library that tried to solve every design or UX problem users would come across.

One potentially controversial choices was reaching for redux. It seems as of late redux has fallen slightly out-of-favor and more people are migrating towards react-specific libraries like recoil. I spent some time researching recoil and ultimately decided it did not fit into my personal design choices. In particular, I don't like how tightly coupled recoil is to react. I find this tight coupling makes it an easier API to work with, but will inevitably lead to issues if I wanted to build a react-native mobile app or a CLI app. Since redux is framework agnostic, battle-tested, and using libraries like robodux I can avoid 90% of the boilerplate.

robodux is great because it promotes the idea that redux is just a local database. I can create database tables which translate to slices in the redux world.

Building a redux slice that has: action types, action creators, and a reducer with a common set of table operations like: set, add, patch, and remove can be written in a single line of code:

 1import { createTable } from `robodux`;
 2
 3interface List {
 4  id: string;
 5  name: string;
 6  ownerId: string;
 7}
 8
 9const slice = createTable<List>({ name: 'LISTS' });
10/*
11{
12  actions: {
13    add,
14    set,
15    remove,
16    patch
17  },
18  reducer,
19  getSelectors,
20}
21*/

redux-cofx is another library I wrote that is a hybrid between redux-saga and redux-thunk. Instead of wiring generator functions up to sagas, I simply create a function like thunks that activate a generator function and still leverage the API of redux-saga. For example, if I want to fetch some lists, I would write something like this:

 1import { select, call, batch, createEffects } from 'redux-cofx';
 2import { selectHasTokenExpired } from '@app/token';
 3import { apiFetch } from '@app/fetch';
 4
 5// API is very similar to redux-saga
 6export function* onFetchLists() {
 7  const hasTokenExpired = yield select(selectHasTokenExpired);
 8  if (hasTokenExpired) {
 9    return;
10  }
11
12  const resp: ApiFetchResponse<ApiListsResponse> = yield call(
13    apiFetch,
14    '/lists',
15  );
16
17  if (!resp.ok) {
18    return;
19  }
20
21  const users = processUsers(resp.data.users);
22  const lists = processLists(resp.data.lists);
23  // this dispatches multiple actions at the same time without
24  // two copies of the state being generated
25  yield batch([
26    addLists(lists),
27    addUsers(users)
28  ]);
29}
30
31// This is a helper function that will
32// convert a map of action creator names to effects.
33// When we dispatch `fetchLists` it will
34// activate `onFetchLists`: e.g. store.dispatch(fetchLists());
35export { fetchLists } = createEffects({
36  fetchLists: onFetchLists
37});

It's a very useful little library that is a satisfying hybrid between redux-saga and redux-thunk and I encourage anyone who feels like redux-saga is too heavy for their uses to give it a try.

Backend #

On the backend I decided to go with koa. I found the minimalist approach of the library to be aesthetically pleasing and exactly what I want from a web server. Koa doesn't even come bundled with a router, you have to install one yourself, I love that! koa has a great middleware system, adopted from express.

I originally went with prismajs but ultimately found the library is too limited and restrictive. I would highly recommend people use it if they are using graphql, but for a simple RESTful API, I found it couldn't do even the simplest of SQL queries.

So, in the end, I switched to knexjs which, again, plays right into my preferences. It's a query builder. When I think about how to query my data, I really just want to write SQL.

As an aside, I really do not get the fascination with ORMs. SQL is already a DSL, why are we re-inventing the wheel and adding another layer of abstraction? SQL is amazing and more people should be comfortable writing in it.

I ended up writing my own server-side rendering implementation for react. All in, with data loading and getting data loaded into redux I wrote about 300 lines of code. Once I landed on a working implementation, the rest was pretty straight-forward. However, I get why people don't want to keep rebuilding SSR over and over again and end up switching to something like nextjs.

Deployment #

For deployment I tend to lean heavily on docker. I use docker-compose for development and docker-machine for deployment. Since this was a fresh project that I don't know how far I'm going to take it, I didn't want to create an automated build pipeline using CI. I'll briefly describe my deployment lifecycle:

My VM was on Google's Cloud Compute and my domain was hosted on Cloudflare.

Conclusion #

This covers a high-level overview of my tech stack and something I will continue to reach for on new projects. It fits nicely into my development style and I understand how all the pieces work together because I wrote the glue myself.


I have no idea what I'm doing. Subscribe to my rss feed to read more posts.