Rediscover your memories while keeping your privacy.
Powered by TypeScript, Electron, React, Redux, Node.js and TensorFlow.js 🚀
👉 Keep up to date with my next side-projects 👈
There is great software out there that provides image exploration capabilities using machine learning (Google Photos, iCloud), but generally is not build with privacy in mind.
At the end of the day, your pictures are uploaded to a server (which perform the machine learning operations), so you have to trust a third party with your data.
What if we could run image classification and tagging machine learning operations 100% locally? You don't have to trust a server if there is no server 😉
👇 Under this premise, taggr was born 👇
A photo explorer, which uses offline machine learning for enriched exploration.
Build with privacy in mind, all the image processing is performed locally, and no data ever leaves your computer 😊
This is my first electron project, so I iterated multiple times until I settled on a general structure I was happy with (at developer experience and performance levels).
In my case, I found the sweet spot by keeping as close to the web standard as possible, and leveraging the existing web / Node.js tooling.
taggr is composed by two main modules (frontend
, backend
), a shared
module, and a message bus
.
The app is split into two distinct and independent processes, the frontend
and the backend
(each mapping to a main module), for the sake of separation of concerns. Each process runs in an independent Electron process.
Before we cover the modules, lets discuss the communication layer.
The frontend
and backend
modules communicate through an asynchonous and bidirectional message bus.
Its implemented using Electron's IPC module.
The supported events are defined as types in the shared
module, so type-safe handlers can be implemented in either side of the bus. For example in frontend/src/message-bus/index.ts
.
Since the message bus relies on the BrowserWindow.id
, the Electron main process keeps a heartbeat with the render process ids.
The 'face' of the app, this module takes care of all things UI.
It does not hold business logic. It communicates with the backend
for performing business logic operations (through the message bus).
Built with Typescript + React components, following the Atomic Design Principles. I used Storybook for that.
The whole UI is controlled, so it renders deterministically based on props, using pure componets. Note that some state is kept locally with Hooks, but thats UI state (ex. input contents before submission).
Uses the 'smart' and 'dumb' component pattern where only the Page
components have side effects, passed as props by container components. The whole UI can be tested and migrated form Redux and Electron easily. Check frontend/src/components/pages/**/WithStore.tsx
for examples.
In order to deploy the app, the frontend
gets built into static assets and copied over to the backend
module.
The 'brain' of the app, this module focuses on the business logic, processing and persistence of the data.
The module also contains the Electron logic for bootstraping the render processes (one for frontend
and another for the backend
) and for packaging the app.
Written in TypeScript, it operates as a Node.js backed. Runs withing a Electron renderer process, with the Node.js APIs enabled. Source code available in ./electron/renderer-backend/src
The message bus handler triggers transactional scripts, which composes the functionality provided by a service layer.
The service layer uses dependency injection through factory functions, so it can be easily tested with unit tests.
The machine-learning uses classification and object recognition for extracting searchable tags from images, through Tensorflow.
The storage of extracted tags is managed using electron-store.
A type-only module, helps keep type consistency between frontend
and backend
.
It keeps shared data such as the available frontend routes, the supported message bus messages and typed representations of the shared domain entities (such as Image
).
This enables compile-time checks on the touch points at the message bus level. Also, it helps keep domain entities consistently typed accross the app.
The app can be configured to run in development
and production
modes, by setting a variable in the shared
module.
-
development
: the frontend runs in a separate process and is loaded into electron as a url. -
production
: the frontend is loaded from static files, and the backend window is hidden. All the debugging tools and extensions are not mounted.
Requires "node": ">=14.0.0"
and "yarn": "^1.22.0"
.
# install dependencies
yarn
# run unit tests
yarn test:ci
# start app
yarn app
# build app
yarn build
https://github.com/aperkaz/taggr/releases
taggr has been a great side project for the past year, I learned plenty about how Electron works internally, how to structure controlled frontends and had lots of fun 🎉
I have other ideas I want to develop, so I dont plan on working on taggr any time soon.
That said, feel free to fork or open PRs!!
Here are some of the great resources I have leveraged to build taggr, in no particular order: