Background
I felt it fitting to write this article now, while I was in the perfect moment of using a tech stack when you truly believe that there is no greener grass on the other side and you are, in fact, sitting on the best tech stack for your project that currently exists. As I write this I fully realize that I will likely become enamored with an entirely new stack at some point and look back wondering “How did I ever do things that way? What a waste of time!”. Although I can’t see it now, I am sure this article will serve as a reminder of how naive I was to think it cannot get any better.
How do I know this will eventually be replaced? Well, I have been here before. A few short years ago, a REST based C# MVC app with Entity Framework was my ‘perfect stack’. You could fire up a new project, throw in a few models, auto generate your views and, just like that, you had yourself a functioning web app. It was (and still is to be fair) a very mature and easy way to build a REST based web app. The problem? I began to realize that, although you could get an app to an MVP really fast, the customers rarely want an MVP. You must then take that MVP and build it into something that people actually want to use. This is where my relationship with C# MVC stack started to sour.
Why I started looking elsewhere
First off, and most importantly, building with C# MVC is painfully slow. Like fill your coffee, check your email and maybe even respond to one or two before it finishes slow. A slow feedback loop is really a dagger for any stack. As most programmers know, it is a rare occasion when your first test of a program works exactly as intended. In a profession where complexity is everywhere, fast feedback is king, and from my experience the C# MVC stack cannot offer that.
Second, I share the belief that REST will eventually be looked at as a miserable thing of the past. Any programmer who has done enough CRUD apps knows the misery of adding a new entity to your database. First you copy-pasta many lines of boilerplate code and tweak a few things just to have basic create, update, delete functionality for your new entity. Then you make your custom endpoints, a new one for any single database function or query that you want to perform. Does your app require authentication? If so, you better make sure you don’t miss a check on an endpoint because your API has full CRUD access to your database (scary).
My last large sticking point is that web apps are getting more and more dynamic. People don’t want to go to a new page to insert or delete and they don’t want to see a page reload every time they change something. More and more, users are expecting a web app to function more like a desktop or mobile app with server communication neatly hidden in the background. This, of course, requires heavy JavaScript use and in any framework that is “HTML first” we run into some serious inefficiencies. An HTML/DOM first UI must always be read and updated manually by the JavaScript code. This is usually done by using an ajax request to your endpoint, parsing the response data, reading the current state of the DOM, and then updating it. This is a lot of work and just another place for bugs and code smells to creep in. Since you are taking a static page and modifying it with many layers of JavaScript complexity things can get ugly in a hurry. The front end code for a page often has little reflection of what the final page looks like. Objects are inserted and removed at will, things that you expect to be there can be gone or changed. It takes crazy discipline and time to keep this even somewhat manageable, certainly not ideal.
It is worth noting that I am not trying to rip C# MVC, REST, or any of the other more traditional methods I discussed as they all have their place, I just thought it important to explain the things I didn’t like in order to show how I believe that the stack I use now is far superior.
The “perfect” stack
Front End
I was turned on to React a year or two ago and quickly realized its benefits for UI development. It makes so much sense to start with JavaScript to generate the DOM, not use JavaScript to read and modify it. State management in React is beautiful. Simply update a state object and the UI is re-rendered to match it. See the example below:
The snippet below shows the traditional way (jQuery) to update the UI. The original values are stored in the DOM, usually generated by some server side code (unknown to your front end) and you modify the value in the DOM (also unknown to your program until it is read in)
<div>
<ul id="items">
<li>Item 1</li>
<li>Item 2</li>
</ul>
<button>Add Item</button>
</div>
$("button").click(function() {
$("#items").append(`<li>Item ${$("#items li").length + 1}</li>`);
});
The following code shows identical functionality in React.
const ItemList = () => {
const [items, setItems] = useState(["Item 1", "Item 2"])
return (
<div>
<ul id="items">
{items.map((item) => {
return <li>{item}</li>
})}
</ul>
<button onClick={() => setItems([...items, "Item " + (items.length+1)])}>
Add Item
</button>
</div>
)
}
The items in the list initially do not need to be read from the DOM as they are known to us through the initial state in the state variable items
. You then call setState and React re-renders the component, updating parts of the DOM as needed. The largest difference here is you are using JavaScript to render the DOM, not to read and modify it. We can use the items
state variable for logic, other UI elements, pass it to other components, etc. and the UI will always stay in sync because the JavaScript code is generating it. Though this is a simplistic example, you can imagine that we would normally be making a API request to add a new item and then rendering on result, this is where it React really shines.
React also gives you insane refactorability and reusability out of the box. For example, to use ItemList
in other components simply do the following:
const OtherComponent1 = () => {
return (
<div>
I am another component<br />
<ItemList />
</div>
)
}
const OtherComponent2 = () => {
return (
<div>
I am another component<br />
<ItemList />
</div>
)
}
You are, in essence, creating your own reusable HTML tags that house your custom UI and logic.
Another handy thing in React is the ability use your state variable in a child component as below:
const ItemList = () => {
const [items, setItems] = useState(["Item 1", "Item 2"])
return (
....
<OtherComponent1 items={items} />
....
)
}
const OtherComponent1 = (props) => {
return (
<div>
There are {props.items.length} items in the list
</div>
)
}
Both components now render whenever we call the setItems function to modify the functions list
I started to use React for everything I could, web apps, mobile apps using React Native, and even desktop apps using Electron. React along with awesome UI libraries like Ant Design and MaterialUI allowed me to snap together complex UIs in fractions of the time and the code remained extremely refactorable and clean.
Life got even better when I started to use TypeScript instead of JavaScript. As you can see above, the components don’t have any type checking. Though there is a little more code needed when using TypeScript and the syntax is admittedly a little wonky, the sanity brought to your software by using types is well worth the extra effort up front. Autocomplete actually works, compile time errors can catch problems before they crop up (less errors in production), and following code through is much easier. There isn’t any guessing what variables an object contains or what a function expects to receive. Within days of using TypeScript I was a believer that it is all but necessary in any JavaScript project. The time you save in maintenance and updates by using TypeScript far outweighs the little extra effort on the front end.
Here is one of the example React components above with TypeScript
const SimplePropTypesExample = () => {
const [items, setItems] = useState<string[]>(["Item 1", "Item 2"])
return (
<ListLength items={items} visible={true} />
)
}
interface ListLengthProps {
items:string[]
visible:boolean
}
const ListLength = (props:ListLengthProps) => {
return (
{props.visible ? (
<div>
There are {props.items.length} items in the list
</div>
) : null}
)
}
Now, if we don’t send the correctly typed props to the ListItemsLength
component we will get a nice error before we even run the code which means less errors in production.
Back End
At this point, I had a pretty good front end stack but the back end was still dragging down my efficiency. This is when I was introduced to GraphQL and Postgraphile. Because I couldn’t say it better, here is a quote from the Postgraphile website on what it does:
PostGraphile automatically detects tables, columns, indexes, relationships, views, types, functions, comments, and more — providing a GraphQL server that is highly intelligent about your data, and that automatically updates itself without restarting when you modify your database.
Just like that the REST API was gone. The endless monotony of writing and modifying endpoints to match every single database change was ancient history. Back end updates take seconds, not minutes, mostly because the back end is practically non-existent to the programmer. Since the server side API is auto generated, updating the database is really the only part of the back end you need to worry about. If you don’t know what GraphQL is, the idea of an auto generated API will likely make you extremely skeptical but check it out https://graphql.org/ and you should see how this can be much more easily auto generated than a traditional REST API.
Here is an old but good video showing the Postgraphile (or PostGraphQL as it was called back then) auto generation in action
What about security you ask? Postgres has “Row Level Security” which, along with normal database roles, provides the authorization layer for your entire web app. No longer do you have to stress about protecting every endpoint. The security is on the database itself, right where it really should be.
Compared to this Postgres+Postgraphile+TypeScript+React stack, a traditional web app has far too many sources of truth for your data and data structure. There is the database, the data that the API returns, the JavaScript parsed version of the data, and what was actually updated correctly and shown to the user. Modifying the database requires a correct update on every single part of this chain, none of which are able to be easily type checked. Errors are only visible at runtime and easily missed only to be found by your users in production.
By using Postgraphile I not only remove a gigantic portion of the work, but also the massive uncertainly that comes with traditional methods. It allows you to have source of truth for your data, the database. When you modify the database, Postgraphile automagically updates the GraphQL API. You then add any new queries and mutations that you plan to use to .graphql
files and a GraphQL code generator (ex. https://graphql-code-generator.com/) will create the TypeScript types from your GraphQL API and React hooks from the .graphql
files to query or mutate your data (CRUD). When set up correctly all of this happens within seconds of updating the database, all without stopping or restarting the app manually. If you have broken anything along the way you get a beautiful error telling you so. This is an extremely painless way for the UI to always stay aligned with the database. I can now add a field to a database and have it displayed on the page faster than I could create and run the migration in visual studio. It is pretty incredible how efficient you can be.
A great demo project (though quite complex) for this stack is https://github.com/graphile/starter. It will take a bit to wrap your head around but, consider yourself warned, you can’t unsee it. Once you are enlightened, working in any other way will feel pretty miserable.
We at Laurium Labs (https://www.lauriumlabs.com/) are using this stack on several projects. The one difference we have is that we use Apollo Codegen (https://github.com/apollographql/apollo-tooling#code-generation) instead of the Code Generator mentioned in the article. We tried both code generators and Apollo generates intermediate types which we use extensively in our codebase.
I’d also like to comment that we effectively onboarded an intern to this stack. The guard rails provided by GraphQL and TypeScript make learning a new codebase far easier.
Lastly I’d like to mention that Postgraphile is “zero buy in”. This means there is no Postgraphile syntax or Postgraphile language. So if you decided Postgraphile is garbage, you could just implement your own GraphQL/REST server that gets data from your Postgres database that you know inside out. That’s probably the most mindblowing part of Postgraphile.