Technical tips to build a SaaS that runs and syncs on 5 platforms
Hi, it’s Takuya here. I’m the solo developer of Inkdrop — a cross-platform Markdown note-taking app that supports macOS, Linux, Windows, Android and iOS. As you know, it is hard to make your app support those 5 platforms alone but not impossible by leveraging powerful frameworks. Not only relying on them, but also you need some development strategies to keep the project sustainable. In this article, I would like to share how I did for this project so far.
Inkdrop for mobile built with React Native
You are not developing alone
Developing cross-platform apps usually involves many unreproducible and unpredictable issues. Features working fine on your environment sometimes don’t work on other environments as expected. For example, I experienced such type of bugs recently, which was that the app launches with blank screen. I have tested it carefully with some beta releases beforehand but I couldn’t know that bug. Thanks to their quick responses and reports, I could solve it within 24 hours.
Solo developers are not actually developing their apps alone because you can always ask your users for help. Be honest and explain what you are working on and what you are struggling to solve. Users who enjoy your app basically hope your app success as well and are happy to help you. Relying on users’ support is important because your resource is quite limited. It would not have been possible to make Inkdrop mature without all its users’ support. As Inkdrop is for programmers, some guys even suggested me a code example to solve an issue like this case on the forum. Moreover, some users have helped me solve a bug which I can’t reproduce by working with me for several hours. Those close and interactive communications with users would be a big advantage that you can stand out against big companies.
CouchDB and PouchDB for seamless data sync with offline support
Apache CouchDB is a document-oriented database(NoSQL) that has HTTP-based JSON API and supports multi-master sync. It has a web-based GUI called Fauxton which lets you manage databases and documents easily. PouchDB is a JavaScript database inspired by CouchDB, which can sync with it. First I built the desktop version and didn’t care about mobile platforms because I assumed that PouchDB works as well on mobile because it’s written in JavaScript and there seems to be some options already. If it does not work, I could build my own client database module as CouchDB is RESTful.
Why I’ve chosen CouchDB instead of other PaaS (Platform-as-a-Service) which supports syncing data across devices like firebase is because I needed flexible client-side indexing support. 3 years ago when I started this project, firestore was not so flexible to accomplish that yet. PouchDB looked promising as it supports client-side MapReduce to build indices and has full-text search module. Besides, there is a DBaaS for CouchDB called Cloudant which can elastically scales throughput and storage independently. As I don’t feel like operating servers, I started using Cloudant in the early days. Currently I’m operating CouchDB on AWS EC2 since I found that the server load is basically stable and not so heavy like BtoC services than I expected and Cloudant was very sluggish and not comfortable to manage on IBM Cloud.
Overall I am happy with CouchDB and PouchDB so far.
Cross-platform frameworks
To make it run on 5 platforms, I use Electron for desktop OSs(Windows/Linux/macOS) and React Native for mobile OSs(iOS/Android). Both versions use the same frameworks which are ReactJS and Redux. Thanks to those frameworks, I could build both basically only with JavaScript and that allows the app to share a lot of code between desktop and mobile.
Learn from successful products
It was the first time I build the desktop app based on Electron and ReactJS. But I wanted to roll it out as soon as possible in order to validate my app idea. So, I decided to learn the current best practices on how to build a beautiful UI and extensible architecture from existing open-source projects like Atom editor and Kitematic. I found that their codebase can be reusable to build my app because their project license were not GPL. Thanks, GitHub and Docker.
I started from forking the Kitematic repository. It gave me many good practices and let me avoid a lot of issues beforehand, but there was one drawback — It also brought me some technical debt, as many other projects usually have theirs. For example, it depends on AltJS which is one of flux architecture implementation. The project has become inactive quickly after I started working on Inkdrop, unfortunately. That blocked me to update React to 16 for a while. I eventually replaced AltJS with Redux recently. However, it is basically not possible to anticipate as Kitematic adopted. You can learn a lot from those projects. So, I would do this again if I were in the same situation.
I borrowed Atom’s code a lot for implementing plugin system, keymap customizations, themes and so on. I’ve been always fascinated while reading their code. I would have never been possible to implement them alone.
Share the codebase
Since both desktop and mobile apps are built with PouchDB, React and Redux, they can share a lot of codebase like data models, some redux actions and reducers which don’t depend on native layers or platforms. That makes me quite easy to maintain both apps at the same time.
UI components are not cross-platform
I built UIs for desktop and mobile separately. Since I started with the desktop app, I didn’t care about how I build the mobile app in the first place. Besides, I think sharing UI components between mobile and desktop is not a good strategy, particularly for Inkdrop. Because libraries like react-native-web is basically mobile-first and does not focus on desktops. So, it works only if your app is enough simple and mainly targets at mobile users.
The desktop app uses Semantic UI and the mobile app uses Native Base for building beautiful components with theming support. I’ve written more about my experience on React Native development in this article:
Hacking PouchDB to let it run on React Native
PouchDB works well on browsers and Electron apps. But when it comes to React Native apps, there were several issues — It was not performant when it runs with pouchdb-react-native since it uses AsyncStorage for its adapter. So I built react-native-sqlite-2 and pouchdb-adapter-react-native-sqlite in order to use SQLite as its adapter. Another problem was that it can’t handle attachments well. I had to hack PouchDB core modules to support them:
Hacking PouchDB to Use It on React Native
How I get PouchDB to work on React Native with Attachment Support
Local full-text search with SQLite3
In the early days, I’ve been using pouchdb-quick-search for providing full-text search but it was not performant very well, especially on Android. So I decided to build my own FTS module using SQLite’s full-text search feature based on pouchdb-quick-search.
Making decisions on which framework you should use is usually difficult — but don’t be afraid to try them. Everyone even doesn’t know which one is the best for your project because there are no “one size fits all” solutions. I had to make some libraries myself to solve issues but I’d think those were also good opportunities to contribute to the OSS community. As your project grows, you grow as well. Hope it’s helpful :)
Follow me
This article was originally posted on my blog on Medium here.
Top comments (10)
Why did you choose a self hosted NoSQL on AWS EC2 over AWS DynamoDB Serverless NoSQL?
Implementing data sync feature is complicated. I believe DynamoDB doesn't have such feature out of the box.
that is correct, you'll have to build the sync feature yourself. With that said, you'll gain on improved performance/scalability and no server management.
You are right.
In terms of scalability, I have a policy that scaling should be considered only when it has to scale.
I wanted to release the app as quick as possible in order to check if it solves people's itch.
Besides, Inkdrop is for professionals in a niche market.
Looks like I still don't have to massively scale out.
From feature perspective, I really think PouchDB & CouchDB were a great choice for my app as it supports revisions so you can restore old revision, which is handy to support edit history.
Changes feed is also awesome to support backup feature.
So, I'm totally happy with CouchDB and PouchDB with self-hosting.
it's an interesting database, I haven't considered using it since I'm so into the Amazon services world. Thanks for the explanation!
I also started solo project web service development from this month with concurrently freelance remote work 3~4 days per week.
So this post give me encouragement to show potential of a one human.
Honestly I think your superman because your productivity is special from in mediocre person eyes.
Even though I got motivation from your story, that's great role model for such us me! 🤗
Hi Takuya, I'm following your amazing story with Inkdrop since you start publishing about it, i was wondering how you handle user registration/login/auth i suppose you have a separate backend/Server for that purpose beside the CouchDB for the notes data
Amazing job takuya
Thanks for another great article Takuya!
Thanks for reading!