IPO Track – A glimpse on building a modern app, by example

IPO Track – A glimpse on building a modern app, by example

In this article, you'll see what's the focus on the development process of a modern web application

In this article I’ll show you how I approach product development by example, what I believe are the core values and what you should strive for when you’re building any modern application, less around the process and more around the goals.

The values shared here rose from several years of product development in the Volkswagen Group for apps that scale globally.

This particular application concept is really simple, and it was an idea standing in my notebook for a while: **Get an email alert when some company is scheduled for an IPO (Initial Public Offerring). 🔔

It is also my entry for the Hashnode + Netlify hackathon.

image.png

Why? – The Use Case

Starting with why, the first thing that any product or application (or anything really) needs is a purpose. The use case. The why.

As so, allow me to give you some context on how this became a note in my notebook.

A few months ago I found a new technology that I really got immediately in love with, and there was 1 company that was mostly responsible for its growth and popularity.

I won't dwell on what company or technology it was because it really is out of scope, and I don't want to make this article very tough to digest.

On that note, I had some spare money and figured out I wanted to invest in it. Although, the company wasn't public yet. It was a startup entering the grow-up phase.

With that in mind, I wanted to jump on board as soon as possible, and that could only be through stock buying, so, when the company made an IPO (Initial public offering) I wanted to be the first to know. What I didn't want was to stick around and weekly be consulting IPO calendars.

So that's how the idea began. Having an app that simply gets an email, a keyword and cross-checks that with the latest upcoming IPOs, looking for matches.

Goals & Requirements

There were many goals here. Most of them were forced due to development curiosity. But there were two that outstood:

  • High maintainability. Having something that is easily maintainable and extensible is crucial for any project that wants to survive, especially when it's being developed in free time. **
  • Low running cost. As this project was not intended to make any money, keeping the costs low was crucial to not shutting it down after 1 or 2 months.

Then there were architecture requirements and development constraints, but again this is was mainly forced due to my curiosity:

  • Serverless backend. I really believe that serverless and edge computing is the way to go. They are a perfect way to increase performance, cut down costs and increase maintainability by not having to worry about server scalability and availability.
  • Static frontend. A static webpage is a webpage that does not do any server-side computing at runtime when delivering you the HTML content. This highly increases performance as you don’t need to wait for server processing, hence you can serve it through the CDN. It is a step back in frontend complexity and paired with the already versatile HTML+CSS it allows beautiful compositions, while keeping the process simple.
  • Neumorphic design. This is clearly a soft requirement, but I believe neumorphic design has lots of potential. The challenge lies in keeping a balance between aesthetics and usability. I achieved this by using shade and color, like in this button:

    submit button for ipo-track 👍 submit button for ipo-track 👍

    Typical unactionable Neumorphic button Typical unactionable Neumorphic button 👎

  • Infrastructure as code. I like to say that there are always at least two people programming, you and you in 3 months. I wanted to make the repo “forget-proof”, and I was definitely going to forget my infrastructure at some point in time. Having a centralised place to check all that is needed for my app to work was essential to meet my primary goal – maintainability.

  • Backend in AWS. In Volkswagen we are undergoing an infrastructure migration to AWS so learning more about AWS was definitely something I was going after. It is the largest cloud provider and has some really good tools to help with IaC (CDK, SAM, etc) so this was not really an effort.
  • Monorepo. One thing that I absolutely hate is having to browse through several databases of information to understand a codebase. If it is needed for it to work, then it needs to be in the repo. No clicking around UI on some cloud provider or detached frontend and backend.
  • CI/CD. This is mandatory for any modern web app to properly mature. We live in a fast world and the ability to be flexible while still robust is absolutely a priority.

Infrastructure and Backend

The infrastructure was all done in AWS through the cloud development kit.

I decided to create two environments so that I could freely play around in a production-like environment without having the fear to break it. With so, I decided to adapt my CDK code to cope with such a goal.

The way I did it was by providing an environment variable that stated the environment that I'm in, adapting the infrastructure accordingly if needed.

🗒️ I'm not very happy with this solution – using CDK deploy command as a per environment deploy – but I was not very confident in any alternative. Feel free to share how you've handled multiple environments while using CDK for IaC and how you've dealt with slight architectural changes among them.

Here follows a diagram of the infrastructure that I went with

ipo.drawio (4).png

This is basically two steps:

  • User Subscription – After the user gets the HTML from the globally available (and performant) CDN, a subscriber lambda is called which will add both the user and the activation date to the DB, sending a "Welcome" email afterwards.
  • User Notification – A daily cron triggers another lambda that queries both the users DB and the IPO API, matching the keywords with the Company names (case insensitive), sending the email and deactivating the account in case of a match.

❗ I ended up not using SQS/SNS to deal with subscribe events approaching it in an event-driven way, and I regret that. I went with lambdas mostly because they were cheap and easier to set up. I misplanned the time this all would take me, and unfortunately, I did not have the time to do this infrastructure improvement. If I ever get too many concurrent subscribers this architecture will most likely fail, due to concurrent writing.

The lambdas were written in Javascript and were tested with Jest in a Behaviour Driven way, favouring integration tests over classical unit testing. This improves maintainability hugely, and if you remember, that was my top 1 priority.

You can read more about this in an article I’ve written especially about this.

There is also the contact flow:

a.png ipo-contact.drawio.png

That works basically the same way as above. The user triggers a lambda that sends an email to the IPO Track team (of one individual).

Frontend

Performance

It was simple to opt for a static website since my application has no client-side state. Building my HTML and only serving static files greatly increased performance, as it eliminates the need of having to do any server-side computations, even when changing pages. This is a reason that helped me achieve such good metrics on web vitals.

Metrics.png

I went with NextJS on the frontend, but honestly I didn’t need to. What I’ve done was mostly write HTML and CSS. I used Javascript for some features (like when you click on the discord logo) – but this was so small that it could have been pure Javascript.

🗒️ Always try to minimize the number of tools & packages that your application needs and uses. You will learn much faster because you will be developing at a lower level, thus reducing bundle sizes, increasing performance and maintainability since there are fewer frameworks and libraries to configure, load, and maintain.

I regret choosing NextJS now, since it was not specially designed as a Static Site Generator, and gave me some headaches when referencing other HTML pages within the code. If I went back I would have just something simple for tooling, such as Vite.

SEO & Accessiblity

I was in for all of those 💯 badges baby! As so, I’ve really tried to follow semantic HTML (like using the section tags) and properly include meta tags.

Meta tags.png

I like to think always in a user-driven way. How can I make the experience easier and the most useful for most people? This is a great mindset for fulfilling both SEO and Accessibility requirements, since Page ranking algorithms try to mimic the real impression of a user. Pursuing the user and not the actual page ranking classification is a nice way to ensure that even if this page ranking algorithms change, you will cope with them.

Testing

Testing is not a sidekick of the code, it is the backbone. You should care more about your tests than about your code. Proper tests will ensure proper code (at least properly functional), but not the other way around. Also, implementation code changes a lot. If testing is done right (in a behaviour-driven fashion) after you write them, you’ll only have to change it if your application basic functionality also changes, which is very unlikely.

Having said this, and as I am a huge maintainability freak, I advocate for an unconventional way of testing, resembling more the testing diamond instead of the testing pyramid:

testing.png

I’ve done an whole article explaining the way I approach testing, so I’ll not dwell too deep here. In this particular project I’ve decided to go with some industry known strategies, that in my opinion help me achieve an acceptable degree of confidence in my tests:

  • Staging Environment – Having another environment that you use for testing your code in a real world-like scenario. Mine is called “sandbox”, and it is close to production as possible. (It uses a different IPO API and deploys to a different URL). Here is how the pipeline in Github Actions looks with it:

Untitled.png

  • TDD (Test Driven Development) – Only write implementation code after you have a failing test.
  • BDD (Behaviour Driven Development) – Use business and user behaviour as a guideline for developing and testing. **
  • Obey the Refactoring rule – If we’re only interested in testing behaviour, it logically comes that refactoring shouldn’t make tests fail.

    “Refactoring is a disciplined technique for restructuring an existing body of code, altering its internal structure without changing its external behaviour.” — Martin Fowler

Conclusion

In order to properly construct a modern application, there’s a lot that you should care about. You should have your goals properly set, you should be aware of the newest technologies, and you should try to deliver value with the least amount of tools and libraries.

⭐️ I’ve written an article to share an actionable way to classify your app’s maintainability and overall easiness/quality of development. Click here if you want to learn more.

I’ve also used this as an opportunity to participate in the Hashnode hackathon, but most importantly, to learn about new technologies. Here's a wrap up of them:

  • AWS CDK that I used to back my Infrastructure as Code.
  • AWS lambdas, API Gateway, EventBridge and SES, that I used to do the backend logic and trigger email sending
  • DynamoDB that I used as my key-value datastore.
  • Github Actions as a free-to-use and reliable CI/CD system
  • Neumorphic design and the use of color
  • Tailwind CSS for having some "scroll-free" way of seeing styles.
  • Next JS to build and export my static files
  • Netlify as a distributor for my static files and DNS provider

All of this while being cleaner than 90% of internet websites tested on websitecarbon.com, and delivering almost perfect web vitals reports 🙂

image.png

Here is a link for the full source code if you are interested! Feel free to take a look and give your opinions 😃