Web App Development Retrospective

I developed a web app for my wife's landscape design business. It's been in use for a couple of years now. This is a summary of the technology, and some of the challenges I faced.




I built a web app for my wife’s landscape design business. It’s been in use for roughly two years now, so it’s about time to do a retrospective to reflect on how it’s working, and how it has been to build and maintain.

This will be more technology-focused than what you might expect from a traditional product case study.

Here’s the thing: I don’t love doing “product work”, but I still get the occasional itch to try and build something useful and user-facing. Also, what better person to work with (and hopefully impress) than my spouse?

Niccole creates landscape design plans for her clients. We created an app to address two main things:

  1. Give Niccole at Primula Horticulture & Design a centralized place to manage her projects and communicate the updates with clients. Project updates include things like contract signing, file uploads, and collecting feedback throughout the design process. Prior to the new app, all of these things happened via phone calls, text messages, and emails, and so it felt disorganized for Niccole.

  2. Make it easy for her clients to see the status of their project(s). Clients were generally satisfied communicating via phone and email prior to the new application, but it was sometimes difficult for them to know what point of the design process that their project was in. There were also some occasional file sharing issues, since this would happen mainly over email.

Web vs Mobile/Native

We chose web app instead of a native app because:

  1. Well, I don’t know native app development, and I’m not about to learn that crap.

  2. A fair amount of clients fit an older demographic, who have a hard enough time simply using email. To require clients to download an app didn’t seem right. Client age aside, I personally hate when I have to install an app. I know this may be a personal bias, though.

  3. A web app can be used from basically any device. Niccole needs to use the app from her computer, while her clients should have the freedom to access it however they prefer.

Overall Stack

The app is hosted on Fly.io. It’s an Elixir/Phoenix app, backed by a Postgres database.

I used Phoenix LiveView instead of something like React.

There’s also a dedicated server to handle PDF generation, using Gotenberg.

Other miscellaneous pieces of the puzzle are: S3 for file storage; Twilio for SMS messaging; SendGrid for emails; and Tailwind UI for good looks.

Thoughts on Fly.io

I’m pretty happy with Fly.io overall. It’s simple to deploy your app using their CLI. It’s nice that you can deploy your app close to your users. Especially since Niccole’s clients are all local.

Fly seems to be one of the most Elixir-centric “Platform as a Service” companies (even before Chris McCord began working there), so this was a factor that led me to try them out in production.

I have noticed some disappointment on Hacker News around reliability issues, but I haven’t had much trouble with our app over the course of two years. Probably because it’s relatively small potatoes.

I did run into one noteworthy problem where I could no longer SSH into my Postgres server, due to a problematic change on their end. The support team was quick to respond, but the solution was to make a copy of the database and deploy a fresh instance. It all worked out fine in the end.

Phoenix LiveView In Production

Not all of the app required that SPA-feel, but here are a few features that we did want to have a real-time user experience:

It’s great that I didn’t have to build a separate front-end application, with a REST API to send it data.

My main pain point with LiveView was that it’s still not quite at v1.0. The last time I experimented with LiveView was about 4 years ago, with v0.4.0. As you can imagine, v0.4 is a lot different than v0.17.5, which is what our app runs (and is now very outdated).

All the change is a good thing, because LiveView is much more powerful now than before, but it really took me by surprise. I found it difficult to understand some of the new changes. I felt as though my code was immediately outdated. It was difficult to find up-to-date information beyond the official docs. Updating UI components became quite tedious and repetitive because some of the component abstractions like slots, Phoenix.Component, Phoenix.LiveComponent just didn’t quite click for me. I am not proud of some of the code I ended up with.

I think we can chalk it up as a skill issue on my part, though. I would still pick LiveView again if I had a do-over. I don’t find it as immediately intuitive as writing React components, though.


I chose Elixir simply because I’m most familiar with it. There are a lot of things that make Elixir awesome (out of scope for this article). For the sake of this app, I could have chosen basically any language that has a web framework and been fine.

PDF Generation with Gotenberg

A key part of the app was to generate a PDF document, which is a signed Signature of Work agreement. The process goes like this:

  1. The Client is directed to the SoW page after account creation.
  2. The Client reads the agreement and may choose to sign and accept.
    • It’s just a LiveView-backed HTML page where the user can draw their signature.
  3. Upon acceptance, a static version of the signed HTML page is sent to the Gotenberg service.
  4. The Gotenberg service responds with the PDF document. It’s uploaded to S3, and made available on the client’s dashboard.

Gotenberg’s URL to PDF route works like a charm. It’s simple to deploy as well, since it’s literally just a one-line Dockerfile.

FROM gotenberg/gotenberg:7.3.1

Tailwind UI Components

I ended up purchasing access to the library of pre-made UI components from Tailwind CSS. Front-end is not my forte, so I found that it was worth my time and money to take this shortcut.

There’s a large amount of components to pick from, and they are pretty easy to tweak, so I’d recommend doing this if you don’t love front-end, but still need a pretty app.

App Reception

One of the goals was to give Niccole a centralized location to manage her projects, and communicate with clients. She was heavily involved in the planning and design of the app, so it has met her needs quite well. We made small adjustments to the app over the past couple years as pain points were realized.

Another goal was to notify clients about the status of their project. Niccole’s design process involves several phases, with client feedback needed along the way. Clients are sent notifications via text message and/or email throughout the design process, as the project progresses. Sometimes action is required of them before the project can continue.

Niccole says that seeing others use the app has revealed even more opportunities to improve her client interactions.

We knew that the app would have to work well for a variety of users, each with different levels of technological abilities. Simplicity was one of the main goals. Despite creating an app that both Niccole and I feel is very simple, some clients still struggle and get tripped up.

For example, we didn’t expect so many clients to let their account registration link expire. The expiration is a feature that prevents the Signature of Work document from sitting around, unsigned for too long. We don’t have an automatic way to recover from that scenario. Consequentially, I have spent a lot of time manually changing database fields to reset client accounts.

I think it’s important to realize that the app is not the product here. The product is the landscape design plan that Niccole hands over to the client. So, I wanted this app to be helpful, easy to use, and non-intrusive. I think it’s been a net-positive, but to be honest, I’m not sure if I was able to check all of those boxes.