About a week ago I launched the early access for Elite Ionic. This is my latest course for Ionic, and it is focused on teaching advanced Ionic concepts.
Typically, when I release any paid content it is in the form of an eBook, but this time I wanted to do something a little different. Elite Ionic is an online course, and the course software is a Progressive Web Application built with Ionic. There are a few reasons that I decided to build the course software with Ionic (and I will discuss some of those reasons in this article), but a big part of it was putting my money where my mouth is.
The course is all about learning advanced concepts for Ionic that apply to building real world applications that are complex, well tested, high performing, beautiful, and usable. It seemed fitting that I should use the tech that I am spruiking for the course itself.
It's easy enough to build example applications in an isolated environment, even complex ones, but when you have an application that is being used by hundreds or thousands of users the cracks can start to show, and you learn a few things.
I have yet to build a PWA for a "real" project, so this was a great opportunity to see just how well it would work in the real world. There is no overarching theme to this article, I just think the little tidbits you learn as you deploy a real application to the world can be useful to others, so I will mostly just be writing about everything I think was of relevance to the launch of Elite Ionic.
I consider the early access version of Elite Ionic that I launched to be the Minimum Viable Product (MVP). There is plenty of debate around what exactly an MVP is – the basic idea is that you want to get your product into the hands of users as quickly as possible, not spend a ton of time "perfecting" your application in the dark.
To some, an MVP might mean getting the quickest/dirtiest version of an application out to users for feedback as quickly as possible, and to others, it might mean releasing a polished/finished version of a subset of the total features of the application. I subscribe to the idea of an MVP being a subset of features being well executed.
The term that has resonated most with me (and I forget where I heard it originally) is that it is supposed to be an MVP (Minimum Viable Product), not an MVPoS (Minimum Viable Piece of Shit). In my opinion, an MVP should be a simplified version of your application that still provides value to the user and does its job well. It should be well-tested, fast, and provide a good user experience.
For Elite Ionic, the MVP included (at a high level):
- 3/5 of the course modules (each module exists independently)
- Offline access to modules
- Functioning login and registration system
- Automatic re-authentication
- The ability to view all available modules and lessons
- Well formatted lessons
- A usable interface
and specific features that were left out include:
- 2/5 of the course modules
- A password reset facility
- The ability to mark lessons as being read
- A search facility
- The ability to automatically be taken to the next or previous lesson
All of the features that I have left out of the MVP will detract from the overall experience, but I don't consider these to be detrimental to the experience. The application will be better when those features are added, but a solid experience can still be delivered without them.
This allowed me to focus on the things that mattered most for the initial release, and work on improvements afterward. What I think is a worse approach is to try and implement all of these features, but skimp on the quality, and end up releasing something buggy.
If I wanted to skip tests, I could probably have implemented all of these features in perhaps a quarter of the time. I'm sure that I would have spent a lot more time answering support emails for people that managed to break the application, though. One school of thought I have seen a bit over the years is:
"Ionic is great for building a quick prototype, and then you can build the real thing natively."
This is an easy trap to fall into if you don't put the effort into building a good prototype. Things are done quickly/half-assed and you end up with a barely working pile of junk. To turn that pile of junk into a polished application can be quite a task, so it usually ends up on the trash heap in favour of a fresh start (or worse, the application continues to be developed by adding more junk on top of the existing junk).
If your MVP or prototype is designed with tests from the beginning, and thought is put into designing a small set of features well, you can take your prototype built with Ionic, and then build the real thing in Ionic using the same codebase by improving it – both by adding new features, and by optimising for performance. I think pinning performance issues on a framework is often just a convenient scapegoat.
In order to ensure that the features that are included work well, I used Test Driven Development to develop the course software for Elite Ionic. I talk a lot about the benefits of Test Driven Development in the course, and how you shouldn't cut corners because when you start cutting corners you begin down a treacherous path that leads you away from a high quality, well-tested application. The whole point of TDD is that you write tests before you write code.
…which is why I felt like a bit of a hypocrite when I was adding a few final features to the application days out from the initial launch without writing tests. At launch, about 85% of the application was developed with a strict TDD approach, but I was running short on time and had to implement a few features that weren't tested.
I don't consider this to be any great crime – time constraints and deadlines are common and sometimes quality does end up getting sacrificed. However, it is now one of my top priorities to make sure I go back and add tests for all of those features. Test Driven Development gives you a high degree of confidence in your code, and when tests are missing that confidence is diminished.
Let's talk about the tech I used to build the course. As you know by now, the application was built with Ionic, but it is also using:
- Node/Express hosted on Heroku for the server
- Superlogin for authentication and registration
- SendOwl for sales and license key generation
- CouchDB (Cloudant) as the database
- SendGrid for sending emails
I don't want to dive too deeply into the architecture, so I will attempt to explain it briefly (if it is of interest, I may do another blog post that goes more in-depth into the overall architecture).
When somebody purchases the course through SendOwl they are sent a
License Key and an
Order ID, which can be used to create a new account in the application. When attempting to create a new account, the Node/Express server will verify the Order ID and License Key against SendOwl's API.
If the key is valid, the user's details are passed along to SuperLogin which handles creating the account. After logging into the application, the user is granted access to a shared database on Cloudant that contains all of the content for the course, which is then synced locally to a PouchDB database on their device (which enables offline access and keeps updates in sync). Each lesson is stored in Cloudant as a
document, and each lesson's document has an associated
attachment which is a markdown file. That markdown file contains the content of the lesson, and then the Ionic application renders that Markdown as HTML and applies syntax highlighting to code blocks.
The beautiful thing about this approach for the course software is how quickly and easily I can update the content. Prior to this, I had always released my paid content as eBooks. If I wanted to update something I would need to make the change, create a new PDF, ePUB, and MOBI version of the book, reupload the new version of the book, and email out an update to everybody. This is a difficult process for me, and difficult for the customers who have to re-download the book. This is an especially difficult problem when dealing with a framework like Ionic that is constantly evolving.
Earlier this week, someone sent me an email pointing out a formatting issue in one of the lessons in Elite Ionic. I was able to open up the markdown file for that lesson, and then re-upload it to the document on Cloudant. That update was then instantly available to everybody, and the next time they access the application the change would be synced to their local database.
The costs for running this application are probably a little higher than they need to be, but let me break it down…
The Ionic application itself is just hosted on the same server that I use to host my website – it's just a bunch of static files so that's not an issue.
I'm able to run the Node server on a hobby dyno with Heroku for $7/mo. The server is only used for account registration and authentication, and since mostly only people who have purchased the course will be making these requests, the requests have been reasonably infrequent after the initial launch.
Cloudant charges based on a reserved amount of database operations per second (and a set amount of storage). Their free Lite plan allows for 20 lookups per second, 10 writes per second, and 5 queries per second. If you exceed that capacity, any requests will return an error until the previous operation has cleared. This is reasonably manageable, especially with PouchDB because it can retry failed requests automatically, but if you have too many people using the application it's going to slow things down a lot.
The Lite plan may be enough to sustain the course on a long term basis, but I wanted a little bit of extra breathing room. Cloudant's first paid tier offers 100 lookups, 50 writes, and 5 queries per second for around $75/mo.
In order to send forgot password emails, I am using SendGrid which costs $9.95/mo. This gives me way more capacity than I need (40,000 emails a month), but I will be able to use it in other projects.
The total cost for running the application is around $92/mo – As I mentioned, this is probably overkill, but this is something I am charging people for so I want to make sure it runs properly.
The experience of developing a PWA is mostly the same as developing a normal Ionic application, with a couple of different steps. I was curious to see what kind of issues I would run into when deploying a production PWA, because as I mentioned this was my first "real" PWA, but there are only really two things worth mentioning that I ran into.
A service worker is a critical part of a PWA, and a big part of the value a service worker provides is its ability to cache the assets for your application, which is why a PWA is able to continue working even when offline. It is also why a PWA can be loaded so fast because the static assets are already stored locally.
However, there are a few different caching strategies you can use, and for me, it came down to a decision between:
cacheFirst strategy will first load the resources from the cache, and then download resources through the network if necessary, but a
networkFirst strategy will first attempt to load the assets again from the network, and if the network is not available it will fall back to the cache.
My concern in launching the application was what would happen if I needed to push a fix for the application. I'd done my best to test and check the application, but there is always the chance that when you send it out into the real world that some issues or bugs would pop up. With a
cacheFirst strategy, it would limit my ability to quickly push an update for the application because the old version of the application would continue being served from the user's cache. By using a
networkFirst strategy I can be sure that the user is always using the latest version of the application.
This still gives the application the benefit of being able to work offline (the cached resources will be used if no network connection is available), but it does miss out on the huge benefit of the blazing fast load times you can get with a
cacheFirst strategy. This was a sacrifice I was willing to make for the launch of the application, but I will likely switch back to a
cacheFirst strategy once I am confident any issues have been ironed out.
I'm sure there is a smarter way to do this, for example by invalidating the cache with a
cacheFirst strategy in some manner if an update is available, but this is something I am yet to look into.
The only real actual issue I ran into was a bug with Ionic involving using the browsers back button to navigate between pages in the application. A lot of the time this works fine, but there are circumstances where an error is triggered (in the case of Elite Ionic, this happens when attempting to use the browser back button to go back to the login page), and it seems that there is currently no known fix for it. This is not a deal breaker for me, because you can still use the application by using the navigation controls provided inside of the application, but I think it is a reasonably large issue right now that might put some people off going all-in on a PWA approach with Ionic.
Over the first day of the early access for the course being live, a total of around 200 people purchased the course – a big chunk of those people purchasing within the first hour.
I'd done everything I could to make sure that the application would work as expected, but it's scary to go from nobody using the application to having many people signing up and accessing the application at the same time. I was especially concerned about the provisioned capacity of the Cloudant database and whether that would be enough to keep up with demand.
Things seemed to be going well initially, people were signing up and it seemed to be working until I got the first message from someone saying that the sign-up form kept telling them they couldn't use their username. I knew that wasn't a good sign, and that it indicated something might be wrong with the server.
My first thought was that the asynchronous validation I had set up for the username and email fields (that would automatically check availability against the server) were causing too many requests and crashing the server. When I logged into Heroku to check on the server and saw a message that looked something like this:
In the last 24 hours, there have been 102 critical errors for this application
That was my "Oh shit" moment, and I began scrambling through the logs looking for the reason for the crashes. Fixing the issue whilst sales were coming reminded me strongly of this GIF:
I quickly found that the server was crashing due to unhandled errors. I'd spent so much time ensuring everything else worked, and I took it for granted that my simple server (which was only about 50 lines of code) wouldn't cause any issues – but this is what ended up causing all of the problems that I experienced during launch. Fortunately, the deploy process to Heroku is very quick and easy, so I was able to fix the issues quickly.
…and so my long rambling and sometimes ranting post comes to an end. I did attempt to give this some semblance of flow, but I find case studies of real life experience like this interesting to read, so I thought it may be useful to some people for me to document my experience in launching this application – however disjointed that may be.
If you have any questions, feel free to leave them in the comments!