INTRO
What did we build?
The Share Brewfiles project was built to make it easy for people to share out their full tech stack. We also added a social aspect to the site so that developers could have fun with it.
Here’s what we built:
- A personalized Brewfile page that is easily shared and just requires one CLI command to be updated whenever you want.
- Generates a developer personality profile based off the packages in your Brewfile, really fun to share with friends & social media.
- A leaderboard that lets you see what’s popular overall and discover new tools.
- Search that allows you to search by package or username so you can stalk other developers’ Brewfiles.
Run npx share-brewfiles
in your CLI to try out the tool.
Also, this project is 100% open source. I want to give a huge thank you to Warp (the company I work at) for encouraging me to build this tool. We hope this project contributes to the developer community in a fun and educational way!
Our inspiration
Most developers’ careers can be tracked by the packages they’ve downloaded over time. At school, I downloaded frameworks like Python, Node.js, and Java. Upon graduating, Monokai Pro became my VSCode theme, Droid Sans Mono became my default NerdFont, and I used Starship to customize my terminal prompt. In recent years, I’ve experimented with newer tools like Warp, Raycast, and Github Copilot.
What tools we download tell the story of who we are as developers. We enjoy sharing our hand-selected tools in blogs or Youtube videos titled “Must-Have Tools And Apps” or “How To Customize Your Dev Machine In 2024”. But this content can’t be easily updated as you evolve, and usually only contain 5-10 tools when realistically most developers are probably using >50 packages.
Technical Overview + Tech Stack
Our tool can be split into 2 parts:
1: The command line experience
Stack: Clack CLI, Github auth, Node.js
Run npx share-brewfiles
to generate your Brewfile package list for you if you don’t already have an existing one in that directory. It collects your Github auth and sanitizes your data before uploading it to the brewfiles.com API endpoint. The UI of the command line interface (white vertical lines, spinning and text animations) is dictated by Clack CLI.
2: The brewfiles.com website
Stack: Astro, React, Tailwind CSS, Firebase
Just a reminder that this project is completely open source. This code supports the entire web experience (which we described in the “What did we build?” section) which is too much to cover, but we’ll point out a few interesting chunks of our codebase in case you decide to poke around.
api/uploadBrewfile
This is the endpoint that the CLI logic will hit when uploading a Brewfile to the site. This logic will sanitize the data and check if this user already has an existing Brewfile (and update it if needed) before uploading it to our Firebase database. It will also asynchronously kick off the process of parsing through the packages to determine the developer’s personality type.generatePersonality.ts
This file contains the logic through which we determine a developer’s personality type. You can see all the different possible personality types, as well as the criteria we set to be categorized for each personality. We realize these categories and criteria may be a bit arbitrary, but cut us some slack - we decided to build this feature 2 weeks before launching! If you want to see improvements, send in a PR.labelledBrewfiles.ts
This file is a dictionary of packages categorized by certain characteristics, like whether it’s frontend/backend/devops/ai, whether it’s related to the CLI, and more. This dictionary is what we use to filter our leaderboard by “top dev apps” and “top CLI tools”, as well as what we use to determine a developer’s personality type. We used ChatGPT to help us label these packages.
What we learned
Astro vs React
When choosing a web framework, we settled on Astro. Admittedly, part of this decision was inherent bias. Astro was a technology that tech influencers were claiming to be “S-tier”, and we thought this newer tool would be fun to write about and attract attention from those who were curious. From a more logical standpoint, we chose Astro because SSR rendering meant better performance and better implications for SEO. Plus, it allowed for a high degree of flexibility by simplifying the management of dependencies like React/Vue/Svelte.
One downfall of this approach is that Astro is meant for content-heavy, mostly static websites, whereas the Brewfiles website has many dynamic elements. While content-rich sites have their place, our thoughts about Astro mostly came down to planning. Due to the nature of building the site as we were designing the UI/UX, we continued to make adjustments—each one moving more and more dynamic.
Thankfully, Astro can include React and other UI frameworks with a simple integration, but we lost most of the benefit of Astro as we continued to make the site more dynamic. So while Astro could handle it, a React meta framework would have likely been a cleaner experience. In the end, we were left with a few Astro route shells filled mostly with React components and manual vanilla JavaScript components.
For example, on our website, we have a screen that pops up an auth code that the user needs to copy when uploading their Brewfile to the site. Here’s what it looks like.
When the user presses “Copy Code”, the text on the button will then change to “Copied!”. Here is what the Astro code looks like (or see code here):
As you can see in the code, we need to interact with the DOM directory using JavaScript to add event listeners and perform actions based on those events. This approach is quite manual. It’s clear that Astro encourages developers to pre-render as much content as possible at build time. If we had written this code in React, we could simplify the logic using React’s state management and event handling capabilities. The code may look something like this:
As you can tell here, the use of useState and onClick() is much cleaner than using the <script>
tag to embed Javascript code directly within the HTML markup. Because of the complexity caused by Astro, we ended up rewriting a few of our files using React instead.
Though we had to work around some of Astro’s capabilities as a framework, there were still some upsides, which we’ll talk about in the next section when it comes to rendering and performance.
Sever-Side(SSR) vs client side(CSR) rendering
Astro SSR adapters enable server-side rendering, meaning as routes are requested, we could tell Astro to generate a finalized HTML page on the server. Some pages, however, require processing a lot of data before rendering the page. With each new brewfile submission, more processing will be required, and yet we want to maintain quick page loading speed as our data entries grow.
We decided to server-side render each route and embed slower sections as client-side rendered components. As an example, the homepage includes a leaderboard list at the bottom. Generating the list requires fetching all users’ Brewfiles from the database and calculating a top 10 list.
We created a server API route to handle the calculations and then embedded a client-side React component to hit the endpoint and display the final list. This combination of SSR and CSR allowed for the best possible performance and user experience.
Data decisions
- Basic data sanitation
We had to take into account the possibility that somebody could add random sentences into their Brewfile or generally corrupt the file (since it is just a plain .txt) before uploading it to our site. As a result, we checked for a format of followed by and sanitized the strings of extra quotes and spaces.
- Client-side validation The reason that the CLI logic hits the API endpoint on the website and not Firebase directly is because we wanted to do some client-side validation before uploading the data to the database.
For example, we check if this user already has a Brewfile that’s been uploaded. If yes, then we will update the existing Brewfile with the incoming data so we can ensure that 1 user will only ever have 1 uploaded Brewfile on our site.
- Database/Server-side validation
Firebase implements security rules to lock down read/write access to your documents, but validating the content of your data is less robust. We decided to approach validation in two ways to provide some basic safety for our data.
First, we provided some basic rules for reading and writing to our documents in Firestore rules. While Firebase recently started offering an early-access PostgreSQL database solution, we used its traditional document collections. The security rules ensure any document in the collection follows a basic structure with only a particular set of keys, but you can’t easily ensure the shape of your data.
Secondly, due to some of the limitations of verification in Firestore, we decided to provide more structured validation when fetching documents in our server-side API endpoints. The isValidBrewfile function offers four validating options, one for each piece of data associated with a brewfile. By default, all four options are enabled yet for any particular validation call we can turn off a setting to skip that validation when needed.
Since so much of the UX depends on valid data, this two-tiered validation system means we can trust the data when processing Brewfiles. It’s not iron-clad validation, but good enough for our purposes.
User experience decisions
Overall UI/UX
When thinking of the design, we considered a few things.
First, we are trying to bring some “pizzazz” into Brewfiles, which are normally just normal, boring, black-and-white .txt files. For that reason, the UI should feel cool, sleek, and modern.
Second, package lists are very text-heavy, meaning that there won’t be many opportunities to show off graphics/images on this website. That’s why we chose an in-your-face font and a bold-blue color scheme that would catch people’s eyes.
We didn’t have design resources when first starting this project, so one of our engineers decided to try mocking up some initial designs. Luckily, the UI you see today was created by Kyle, one of our very talented designers - but we thought it would be funny to show you some of the initial mockups:
Brewfile Cards
We had some interesting conversations around the design of our Brewfile card. Originally, we had coded our cards to look like this:
Where the card would show the first 7 packages in the developer’s Brewfile, and highlight packages when a certain keyword was being searched. However, we realized this wouldn’t work if the keyword matched with multiple packages within the Brewfile. For example, just the letter “a” would likely match with many packages. This is why product designers are important! Sadly, we had to rethink the UI for the card, which led to some backtracking in terms of our code and implementation. Here were the options we had come up with:
And you can check out the “All Brewfiles” page to see which option we decided on.
Product decisions + product philosophy
A large part of this project was deciding what features to build. For example, we could have doubled-down on features that analyzed the packages within Brewfiles and gave developers more extensive statistics about the tools they download. We could have also changed the UI to only show the top 10 uploaded packages and only updated it once per week, to encourage people to come back to see the new leaderboard.
The leading philosophy behind the features we decided to build was education and social fun. We wanted this website to be a place where developers could discover new tools, which is why we have a complete leaderboard of every single package that has been uploaded, as well as different filters to sort by different categories. We also wanted to create a sense of community, which is why we required developers to connect a Github account with their Brewfile and added a search functionality. And our “developer personality” summary added a little bit of fun to the whole experience, and encouraged developers to share their Brewfiles out with the world.
Discovering new tech/frameworks
Container queries have been stable in modern browsers since Firefox added them in February 2023 and they currently are supported in 90% of browsers used globally. In short, container queries provide control over styling decisions based on the size of a parent container (instead of @media queries which are based on the size of the viewport).
The cards on the /brewfiles route change sizes as the viewport changes, but consistently with the viewport. At first, the cards take up the full viewport width, continuing to increase until 768px, where the list moves to a two-column layout.
Once again, the cards expand until displaying in three columns at 1280px. A simple @media query would only adjust the typography and other UI elements in a straight line, getting larger with the viewport. However, with container queries, we could adjust all the elements based on the intrinsic size of the cards creating a truly responsive interface!
Conclusion
Future features
Before we end, here are some of the features/improvements we wanted to build, but didn’t get to before we launched.
“Annotating” packages
What if developers could “star” certain packages as ones they used daily or deserved special attention? Or flag other tools as deprecated?
Template Brewfiles
What if we had Brewfile templates for a specific project/project type? For example, if I am a frontend dev setting up a brand new MacOS and can run 1 command to download all packages from a specific template Brewfile.
Closing remarks
We had so much fun creating this tool. It has been such a rewarding experience taking such a boring, black-and-white Brewfile .txt and turning it into a cool, fun, and social experience for the developer community. If you’d like to try it out, here are 2 things you can do.
Run
npx share-brewfiles
in your CLI and follow the instructions to upload your Brewfile.Go to your Brewfile page, generate your developer personality, and share it on social media!
And HUGE thank you again to Warp for encouraging me to build this project.
Thank you!
Top comments (0)