A Peek Inside the Tech Stack of Barkpass
I started writing Barkpass code in March 2019. Since then, I've made 844 code commits to two separate repositories.
Let's open up the hood and walk through the two codebases that make up Barkpass today.
The backend of Barkpass is a Laravel app. Laravel is a popular, modern PHP framework. I've been working in PHP for the last decade — mainly with WordPress — with mixed results.
However, Laravel is a game-changer. It's like the Ruby on Rails for PHP — instant productivity.
The Barkpass backend is hosted on DigitalOcean.
Above: A history of Barkpass Backend commits
The backend of Barkpass serves two important roles in the app architecture:
- Barkpass API
- Barkpass Admin
Let's dig into each.
The Barkpass API is a REST API powered by standard Laravel controllers. This is used by the React client to power each view.
This is a pretty vanilla setup. Maybe the only interesting thing to discuss is authentication.
Why use JWT and not something like out-of-the-box Laravel cookie authentication?
As it turns out, using a simple JWT generator on the server and attaching it to the
Authentication header is a nice, stateless, cross-origin solution. Since the Barkpass API is hosted on a different domain than the Barkpass Client, using an Authentication header means I don't need to worry about CORS when it comes to authentication or cookie sharing.
The Barkpass Admin is powered by Laravel Nova. Nova is a premium administration panel which is officially supported by the Laravel team.
It's the only part of the tech stack that's not open source — I paid money for it!
And it was worth every penny.
Laravel Nova allows you to seamlessly transition your existing database models — like
Form — into interactive CRUD (create, read, update, delete) interfaces.
Nova allows you to define which database columns should be editable, which columns should be visible in a detail or index view, and which columns should be read-only.
Nova offers seemingly-endless customization options, so I was able to integrate custom workflows for certain actions (like "Approve an application") and build custom dashboard widgets.
It's seriously a powerful framework, and it saved me hundreds of hours of development that I would have had to otherwise pour into an administrative dashboard.
This freed me up to focus on the Barkpass Client, which ended up being a completely custom build.
The dashboard application itself is based on Vue, meaning I needed to write Vue code to add any custom interactive functionality to the admin. More on this later.
The Barkpass client app is built using Next.js which is a server-side rendered (SSR) React framework.
It is hosted on Zeit Now, which offers an incredible serverless hosting platform perfectly built for Next.js projects.
Because of the way Zeit's GitHub integration works, every single time I push a branch to GitHub, I get a unique URL of that build to test in my browser. Deploying to production is a matter of simply aliasing the latest build to the production url
This means zero downtime deploys and instant rollbacks if needed.
Zeit is also the creator of the open-source Next.js project. I chose this framework because it radically simplifies front-end development, and it offers SSR support out of the box.
Next's opinionated approach to a
/pages directory is reminiscent of the early days of learning PHP, where you could simply add a
pages/about.php file and be able to view it right away in your browser. In a lot of front-end frameworks, you'd need to add a third-party routing library and an advanced component setup to support separate "pages" in your app.
Next.js makes this dead-simple, and I really like it.
I also knew SSR would be important, because people would likely be using Google to find a given city's dog park registration form.
By ensuring the city landing page, as seen below, is server-rendered, this means search engine crawlers can properly index the page:
The dreaded rewrite
I have to share something with y'all: I rewrote this client-side application halfway through. 🙀🔥
In the world of software engineering, this is a big antipattern. Many engineers will get frustrated with a project after it's been around for a while. Before adding a new big feature, they end up rewriting the project to the latest and greatest technology. This often slows the team down and prevents you from building a mature application.
This engineer was me.
I first wrote this application in Vue.
Vue was the first modern front-end framework that I learned, and I still feel the most comfortable with it.
However, I was also anxious to learn more about the popular React framework. At the same time, I was eager to add SSR support to Vue.
Nuxt.js is the de-facto SSR framework for Vue. But the pristine docs and design system used by Next.js won me over in the end.
Isn't it funny how, in the battle between two very similar frameworks, the one with the more appealing marketing site design wins out?
Considering that Next.js and Zeit Now are maintained by the same company, I decided to pivot and see what I could do in React.
You'll see how much later I started on the React version of the client-side compared to the backend:
However, I'm happy I made the switch. I've learned a ton about React in the process, and I've been able to carry over lessons to my day job where we still use Vue on a daily basis.
Why use a framework at all?
I think the question of whether to use a framework will come up more often in the future. Many developers default to using a framework like Vue or React for every single new project — even if the project could be built using standard server-rendered pages or plain HTML.
There are tradeoffs to be made in productivity, of course — and certainly file size and computing constraints as well.
In the case of Barkpass, I wanted the client-side application to be intuitive and seamless for the end user — many of whom would not be tech-saavy.
The most important part of the client application is the permit application form:
This interface involved many moving parts, including the ability to:
- Start a new application
- Add a pet to that application
- Add details to that pet
- Add documents to that pet
- Add details to that document
- Add types to that document
- Add an expiration date to that type
Because of the nested hierarchy of the data structure, a traditional server-rendered application would have involved building dozens of page views and lots of waiting for the next page to load.
Additionally, I didn't want users to be able to click "Back" in the browser and end up in no-man's land with some partial state rendered.
An interactive form is the perfect use case for a modern front-end framework.
As a companion to the client application, I'm using Storybook.js as my component library and documentation tool.
This has been a lovely addition to my toolbox. I use Storybook to develop new components in isolation, in various states, using mocked data.
It's super helpful to build components like this when the component in question is on the second or third step of a form, for instance.
If it weren't for Storybook, I would be refreshing my browser using data from the server and filling out the same form, over and over again, to get to the second or third step. Old-school!
Newcomers to Storybook — myself included — might be tempted to build out an entire component library before building the app itself. Buttons, inputs, tabs, cards, etc. I'd caution you against this, as it's a form of premature optimization.
I've found luck in building each component into Storybook as I go. This piecemeal approach works really well. I can always add more components down the road!
You can check out the Barkpass Storybook instance at ui.barkpass.com.
So that's Barkpass! I'll probably go more in-depth into each part of the Barkpass tech stack — the pros and cons of each — in future blog posts. For now, I hope you found this useful.