# Matej Bačo - Portfolio Site (Full Content)
I am impact-driven software engineer from Czech Republic in love with everything open source.
URL: https://matejbaco.eu
## Navigation
- Home: /
- Projects: /projects
- Blog: /blog
- About: /about
## Tech Stack
### Frontend
- Svelte
- Astro
- Next.js
- Remix
- Nuxt
- Angular
- HTMX
- Alpine.js
- TailwindCSS
### Backend
- PHP
- Go
- Bun
- Node
- Python
### Database
- SQL
- Redis
- MongoDB
- Appwrite
### Platform
- Docker
- Kubernetes
- RabbitMQ
- Traefik
- Cloudflare
### Entertainment
- Jamstack
- Unity
- Lua
- Arduino
## Work Experience
- Software lead at Appwrite (Oct 2024 - Present)
Website: https://appwrite.io/
- Software engineer at Appwrite (Aug 2021 - Oct 2024)
Website: https://appwrite.io/
- Fullstack developer at Freelance (Jun 2020 - Aug 2021)
Website: https://github.com/meldiron
## Connect
- Discord: https://discord.com/channels/@me/287294735054274560/
- GitHub: https://github.com/meldiron
- LinkedIn: https://www.linkedin.com/in/matejbaco/
- E-mail: mailto:matejbaco2000@gmail.com
## Pages
### Page: about
## The Beginnings
I started programming in 5th grade, and I built a web app for tracking homework. As ugly and buggy as it was, I will never stop being proud of this monstrosity. Soon after, I learned the basics of frontend, backend, and databases - which allowed me to build a [PHP](https://www.php.net/)-based social platform at the age of 14.

_The biggest challenge I faced was querying a database for users that were inactive for more than 5 minutes. I simply couldn't comprehend `user.activeAt >= now - 5min`, and it took me multiple weeks to implement this._
---
Websites weren't my thing, so I switched to gaming and developed many addons, mods, and even standalone games. While exploring [Unity](https://unity.com/), my backend skills backed me up (pun intended) to implement multiplayer and realtime interactions with [Colyseus](https://colyseus.io/) websockets.
As silly as it sounds, even Minecraft servers got a lot of networking complexity under the hood. My greatest achievement was a proxy on top of auto-scaling stateless instances of servers using [CloudNet](https://github.com/CloudNetService/CloudNet), allowing support for as many players as imaginable (for me and all my 3 friends).

---
Software slowly became boring, and I became interested in microcontrollers. [Arduino](https://www.arduino.cc/) and [Raspberry Pi](https://www.raspberrypi.com/) gave me a basic understanding of the Internet of Things, and allowed me to automate my homeworks for technical drawing by making a plotter drawing machine.

_It took me multiple dead SD cards and many burned LEDs to understand the basics of current, voltage, and resistance. While not relevant in my career path, knowing electronics allows me to understand the boundaries of technologies and how to connect them._
---
As you can see, my beginnings were all over the place. For a long time, my source of passion wasn't mastering a skill, but learning what technologies are capable of and finding their (and my) limits. But slowly, I settled.
## Settle and optimize
Over the years, I realized I want to make an impact. I want to build tools, help teachers, students, friends. Everyone. I quickly understood that the most commonly used platform is a web browser, so I aimed most of my focus on web development, becoming a [fullstack developer](https://www.w3schools.com/whatis/whatis_fullstack.asp) in the process.
My very first work experience was a 1-year part-time job building an e-commerce store with a [Node.js](https://nodejs.org/en)-based backend and [React](https://react.dev/) frontend.
This was the most stressful time of my career. Not only did I face many programming challenges, it was also my first time building in a collaborative team, under pressure of deadlines, and the weight of responsibilities to deliver features to customers. It feels boring talking about it now as an adult, but my teenage self had a hard time.

_The most interesting problem I faced was dealing with large nested category trees. To prevent recursive search through the database, I implemented [nested sets](https://en.wikipedia.org/wiki/Nested_set_model). Making it fast and memory-efficient wasn't an easy task for a self-taught developer who had never heard of [big O notation](https://en.wikipedia.org/wiki/Big_O_notation) before._
---
After graduating from school and becoming a freelancer, I found out that speed of development is what makes me money. Throughout a year of freelancing, I developed `Fullstack Boilerplate`, a web app starter kit with most of the processes automated. Soon after, when I realized it was hell to maintain such a project, I was faced with a hard decision - do I continue development of my starter, or do I become dependent on someone else's technology?
I chose to use [Appwrite](https://appwrite.io/), since it was open-sourced, and their GitHub repository provided everything I needed to be as confident with Appwrite as I was with my own boilerplate.
It didn't take long before I started contributing to the community, and later to the source code as well. A magical thing happened when I made my first [pull request](https://github.com/appwrite/appwrite/pull/1308) - removing random 5-second slowness in serverless functions. The founder of Appwrite reached out to me with a job interview offer and an opportunity to become a core member of Appwrite with a full-time contract.

## Welcome to Open source
Appwrite was the opportunity of my life. My [GitHub graph](https://github.com/Meldiron?tab=overview&from=2024-12-01&to=2024-12-29) never looked cleaner, and I built dozens of free applications. I learned to work with every major runtime (Bun, C++, Dart, .NET, Go, Java, Kotlin, Node.js, PHP, Python, Ruby, Swift) and every major framework (Angular, Analog, Flutter, Next.js, Nuxt, Remix, SvelteKit). I also had the opportunity to work on Appwrite Cloud and learn architecture management with [Terraform](https://www.terraform.io/), and Docker cluster orchestration with [Docker Swarm](https://docs.docker.com/engine/swarm/).
As part of marketing efforts (which be the way, when working at startup, expect to become expert at everything), I built clone of Reddit Place canvas, an event allowing everyone around the globe to make permanent contribution to a painting. After sharing it on social media, our application went viral and within 24 hours I learned to scale to millions visitors and thousands of active users. While this was perfect opportunity for Appwrite backend to show what it's capable of, it was also one of my proudest moments as it allowed me to prove to others, and myself, what I am capable of.
Below you can find rendered history of our Canvas over couple of days, including dozens of millions of actions.
The biggest achievement by far was a 0-downtime migration when I migrated hundreds of thousands of projects across a couple of databases with billions of records into new databases, changing data structure in the process. The transfer was done while still serving tens of millions of requests, and the migration was a success with only a 0.015% failure rate that was migrated with downtime afterwards, after confirming it with the affected organizations.
I'm proud of the 0-downtime migration because it allowed me to learn how to develop critical parts of applications safely. Before launching the feature, it was heavily tested, benchmarked, and simulated. I included validation scripts for every step with the ability to retry, rollback, or recover. With this experience, I gained confidence in critical thinking when it comes to preventing downtime.
_As much as I would love to include some visuals from this migration, I respect privacy and security of the this company process. Imagine small team of engineers on in-person meetup rolling back database changes because a script broke all relationships by replaicng document hexadecimal IDs with numeric IDs. The one in the middle who pressed enter, that's me._
## Project and people leadership
Thanks to my career path, I am currently able to gain experience in leading dedicated projects and leading teams of engineers. I am excited to become a great leader and grow my skills even further.
---
## Projects
### Project: Almost Vault
Description: Quickly share passwords and secrets with anyone.
Demo: almost-vault.appwrite.network
GitHub: Meldiron/almost-vault
Published: 2025-09-03
## Vision
A few weeks ago I was browsing [daily.dev](https://daily.dev), and I came across an open source tool [Orange-OpenSource/hurl](https://github.com/Orange-OpenSource/hurl), a command line tool for running HTTP tests. From [hurl.dev](https://hurl.dev/), their homepage, I quickly understood it's a Curl-based HTTP testing framework, and it immediately clicked with me; perfect testing tool for Appwrite Functions.
Just a couple of days later, I discovered [Basecoat](https://basecoatui.com/), a UI library similar to [shadcn/ui](https://ui.shadcn.com/), but with the simplicity of [Bootstrap](https://getbootstrap.com/). A CSS library that provides simple one-word classes, giving components a consistent look, extensible with JS functionality for more complex components. This, in my mind, was a great addition to my toolset; it felt just like [launch.css](https://launch-css.dev/), just with more variations.
And last weekend, at [Appwrite](https://appwrite.io/), we announced a Sites hackathon, and I saw it as the perfect opportunity to try out those tools I wanted to try out anyway.
But what do I build?
I wanted the focus of the web app to be Appwrite Function, an HTTP server, so I could take full advantage of the Hurl testing tool. As I was browsing project ideas, I remembered [Shelve](https://www.shelve.cloud/), a password manager I researched for managing my AI API keys. It had a mini project inside of it, [Shelve Vault](https://vault.shelve.cloud/), a simple platform for sharing secrets with others by sharing a URL.
And so the plan was ready. Build a Shelve Vault alternative, but it's a weekend project, so it won't be as finished as theirs. _(or so I thought)_
So, Almost Shelve Vault. So... Almost Vault.
## Technologies
- [Appwrite](https://appwrite.io/)
- Databases, for storing secrets. With the recently released TablesDB, encrypted attributes for privacy, and atomic operations for counting the amount of reads
- Sites, for deploying the webapp to the internet
- Functions, for running the HTTP REST API
- [Koa.js](https://koajs.com/), an HTTP framework like [Express](https://expressjs.com/), but lightweight
- [Zod](https://zod.dev/), a schema validator
- [Alpine.js](https://alpinejs.dev/), a minimal JavaScript framework for reactive UI
- [Vite](https://vite.dev/), a build tool for modern web apps
- [Axios](https://axios-http.com/), an HTTP client, which I used for proxying requests
- [Hurl](https://hurl.dev/), an HTTP testing tool
- [Basecoat](https://basecoatui.com), a simple UI library
And, like everyone nowadays, AI ([Gemini](https://gemini.google.com/) and [Claude Sonnet](https://claude.ai/)) served as my personal assistant for writing documentation, improving code quality, generating test assertions, adding CI/CD checks, and other time-consuming tasks.
## Screens
A simple homepage with basic information, and a main component for creating a secret.
A secret consists of a value, time to live, and amount of allowed reads.
Time to live supports multiple values beginning at one hour, all the way up to an entire year. While there are use cases for all, I decided to go with 24 hours as the default value, since that is safe while also being enough for most use cases, including remote collaboration.
Amount of reads is typically set to just one, meaning it can only be read once. This serves as a great indication; if the target person cannot read the secret, it might have been compromised. But higher values also make sense, for example two (so I can check if it works), or 30, so all my colleagues can use it.

Because the homepage felt too plain, I added a frequently asked questions section and filled it with basic information both engineers and non-engineers might need.

Once a secret is created, an alert shows a summary of the configuration for a quick double-check, as well as a box that people can screenshot for future reference. A secret ID is provided to be easily copied, or even a URL that can be opened to automatically pre-fill the secret ID.

A person who gets the secret ID can visit the same website, switch the tab to "Decrypt," and paste the secret ID into the input field.
Alternatively, if a URL is shared, once the URL opens, the tab automatically switches to "Decrypt" and pre-fills the secret ID; so all a visitor needs to do is click "Decrypt."

When a secret is decrypted, the API server ensures the TTL meets requirements and at least one read is still available. An atomic operation decrements the read amount while ensuring the value is higher than zero during the operation, to prevent race conditions from allowing multiple reads.
Finally, the API returns the secret to the client to be shown. As part of the response, it also provides the configured TTL, remaining amount of reads, and a timestamp of when the secret was created. This information can be added to the UI later.

## Retrospective
I was pretty surprised that within a few days I managed to rebuild the entire Shelve Vault web app, without missing any major features. All props to Appwrite and Basecoat!
Speaking of Basecoat, it's an amazing UI library. It's exactly like I remember Bootstrap, and I didn't face any problems with it. Everything worked, it had the components I needed, and the documentation was great. I plan to use it again and again, on any project where shadcn is overkill.
I am also very happy I took this opportunity to try out all the recently released features of Appwrite, including TablesDB, atomic operations, and the SDK with object arguments.
Hurl was exactly what I wished it would be! So many times I wrote small APIs in different languages, and I didn't write tests, to avoid learning their testing frameworks and picking the right one. Hurl, with its own `.hurl` files, is a runtime-agnostic testing tool I can use anytime I need E2E tests.
I was pretty confident with Alpine.js for this project, but I was surprised to find out there isn't any simple setup between Alpine.js and Vite that would allow routing and layouts. I will need to keep that in mind for more complex projects - either find a solution in the future, or use more robust frameworks for those use cases.
And finally, AI (LLMs) met my expectations exactly. I only used it for "side quests," which I could quickly validate. It saved me a bunch of time with things I wouldn't otherwise do, like documentation.
## Future plans
My use of Axios was pretty redundant, and I should use the native [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) instead.
After decrypting the secret, the API returns more information, such as TTL (time-to-live), remaining reads, and creation date. I should update the UI to display this information in a user-friendly way.
While the application is responsive, mobile integration can be improved by adding better share functionality with the ability to share it with contacts, in messaging apps, notes, emails, and more.
As the app grows, proper accounts (not just guests) can be implemented to see all secrets that were created and better manage them - delete, increase reads, or extend time-to-live.
If the app becomes used by organizations and agencies, teams and team sharing can be added to simplify working across teams.
And finally, to allow even more users to use Almost Vault, integrations are very important; Starting with a CLI tool, REST API documentation, GitHub action templates, and more.
---
### Project: Auth UI
Description: Fully customizable login flow for any applications.
Demo: authui.site
GitHub: Meldiron/authui
Published: 2024-05-11
## Vision
There are many backend-as-a-service platforms that include authentication and authorization as one of their main features. While this already saves a lot of time, popular platforms such as [Auth0](https://auth0.com/), [Supabase](https://supabase.com/), or [Firebase](https://firebase.google.com/) made auth even simpler by providing login pages out of the box.
I researched how they work, and I liked the simplicity of [Auth0 Universal Login](https://auth0.com/docs/authenticate/login/auth0-universal-login), allowing developers to customize through a simple admin panel. Compared to [Supabase UI Auth](https://ui.supabase.io/components/auth) components, which focus on implementation using React components, I much prefer the simplicity and framework-agnostic approach of Auth0.
With this in mind, I wanted to create a similar Auth UI kit for [Appwrite](https://appwrite.io/). The main focus of the UI kit was:
- Simple setup and use
- Customizable branding
- Platform-agnostic (adapters for popular backends)
- Secure by default
- Free and open-source
## Technologies
- Svelte and SvelteKit
- Pink 1.0 design system
- Appwrite Auth
- Appwrite Databases
- Appwrite Storage
## Screens
The developer is welcomed with the entire application that slowly fades away as the visitor scrolls. Interaction is not possible, and the entire website is locked and overlaid with a prompt to sign in. This design gives visitors a basic understanding of what the website has to offer while prompting them to sign in before getting any value.

A sign-in page into Auth UI offers simple email and password login, alongside GitHub OAuth authentication. While this doesn't show all features available, it already provides a great understanding of what developers can expect from Auth UI when making their own login screens.

Once developers are signed in, they are redirected back to the homepage that is now unlocked and the website can be interacted with. A form to create a new login page is visible, alongside a quick preview of what the page will look like once finished.

As seen throughout the remaining steps in the form, developers can customize the auth page to their liking including its design, copy, and which features are enabled.
They can also preview screens for different features:
- Sign-in with all enabled methods
- Sign-up with email and password
- Password recovery
- Phone OTP login
- E-mail OTP or MagicURL login

As the final step of preparation, developers are asked to pick which backend-as-a-service they are using and configure connection to their project. This ensures all users are registered under their accounts and organizations, instead of registering them under Auth UI (and Auth UI owning the authentication database).

Once developers finish configuration of their auth page, a small celebration copy is shown alongside any remaining steps that need to be done in the console of their backend provider. Developers can now also easily copy a URL or small HTML snippet with a link to their newly created auth page.

As developers keep creating more auth pages with Auth UI, they can find them at the top of the homepage. Sites can be easily edited, removed, or they can re-visit the celebration screen to easily get access to the URL again.

A screen that wasn't visible to developers yet is one that their users can see if they visit the auth page after already signing in. Developers can decide to show it to them, as this page includes account management for easily knowing which user is currently signed in and the ability to sign out.

## Retrospective
After sharing the tool with developers using Appwrite in the community Discord server, hundreds of developers started using it. Feedback from those first adopters allowed me to quickly add highly requested features such as customization of legal URLs or roundness of buttons. Furthermore, in following months during free time, I added dark theme support and kept it up to date with latest Appwrite features such as email OTP.
I also received great feedback for making authorization into Auth UI powered by Auth UI itself. This shows confidence in reliability, design, and features supported by the flow.
After a couple of weeks after release, a community member recorded a video on how to use Auth UI with Appwrite, which gave me a huge confidence boost in this project. Since then Auth UI had a couple of downtimes, due to which multiple developers sent me PMs asking when a fix could be expected. All of this goes to show the project idea was quickly validated and has a bright future.
## Future plans
As Auth UI gains more popularity, I plan to add more features as requested. Some ideas provided already include:
- More account details and option to reset password
- 2-factor challenge support
- Session management
- Ability to link/unlink social accounts
- Ability to delete or block account
---
### Project: FormBin
Description: Simple and powerful backend for your HTML forms.
Demo: formbin.almostapps.eu
GitHub: Meldiron/form-bin
Published: 2025-02-18
## Vision
There are multiple websites providing simple connections between HTML forms and e-mails, such as [Formspree](https://formspree.io/) or [Formcarry](https://formcarry.com/). My main issue with those is expensive pricing and limiting free tiers.
My goal was to create an equally simple HTML form backend, which is free and open source. To keep it 100% free, submissions aren't sent by email, but instead are stored in a database. To have an experience similar to e-mail with notifications, pings, and ability to mark it as read, I decided to integrate with Discord.
While this idea was in my mind for over a year, I decided to work on this during [Leo's mini-hackathon](https://www.twitch.tv/learnwithleon) in collaboration with [Appwrite](https://appwrite.io/). The hackathon only lasted 3 days, out of which I spent 2 days working on FormBin.
## Technologies
- Svelte and SvelteKit
- TailwindCSS (shadcn-svelte)
- Appwrite OAuth (GitHub)
- Appwrite Databases
- Discord webhooks
## Screens
A visitor is presented with a landing page that explains the purpose of the service and quick call to actions to try out the demo or sign in. If an existing user visits the homepage, they are instead presented with a button to manage their forms.

Before a developer implements FormBin, the experience continues to a demo form page, where they can experience how their HTML form may look. A simple form includes a few inputs and a submit button.

After submitting the form, a thank you page is shown as proof of successful redirection after submission. The developer is now prompted to sign in and create their first form with FormBin. Similar to the homepage, if the developer is already signed in, they are presented with a button to manage their forms.

The sign-in page is the most minimal page, with a single button to sign in with GitHub. Once clicked, the user is redirected to GitHub where they authorize access, and then get redirected back to FormBin.

Once authenticated, the user is presented with a list of forms. At the beginning it's empty, so only a button to create a new form is available. Once the user adds a few forms, a list of cards shows each form's name, hostname of redirect URL, and amount of submissions. By clicking a card, the developer can view submissions and change form settings.

Have you noticed the theme toggle in the upper right corner? The entire website is styled with dark and light themes, and you can switch between them by clicking the toggle. The site also automatically detects the user's preferred theme based on their system settings.

The modal to create a new form is very simple with focus on having as few inputs as possible. The form's name is a private name to organize it in FormBin, and the redirect URL is a public URL to which visitors are redirected after submitting the HTML form on the developer's site.

Once the form is created, an onboarding page is shown where developers can learn how to integrate FormBin into their HTML forms. By default, the entire code snippet is presented, which when copied into an HTML page, will be fully functional. Developers are then expected to modify their source code to include the FormBin form, and visit the website locally, on staging, or in production. Finally, developers are guided to submit the form for the very first time before finishing the onboarding flow.
If for any reason developers cannot finish the onboarding but feel confident about using FormBin, they can click the skip button to bypass the onboarding flow. If done by mistake, they can always see the onboarding screen again from the form's settings page.

If developers already have an existing form not connected at all, or connected to another service, they can see steps for integrating with the existing HTML form, and a step-by-step checklist is presented. The rest of the flow remains the same.

Once developers successfully send their first submission and finish onboarding by checking if the submissions were received by FormBin, they are redirected back to the list of their forms with a confetti celebration effect.

By clicking on the form after onboarding, a list of submissions is shown, which now includes at least one submission created by the developer. The submission list supports pagination and presents a "Show more" button if there are more than 25 results. The list of submissions is minimal by default, but when clicked, submissions are expanded to show all details. The details can then be copied, or the submissions can be deleted.

Any delete button on FormBin is protected by a confirmation modal to ensure no accidental deletions occur. Thanks to Appwrite, databases are backed up on a daily basis, so in case of emergency, data can always be recovered.

In form settings, developers can change its configuration, delete it, or integrate it with their Discord server's text channel. Discord integration links to Discord docs that explain how webhooks work, where to generate one, and how to use them.

Once integrated successfully, all new submissions will be sent to the Discord channel with a branded design.

Last but not least, an account page shows the ability to log out from the account.

## Retrospective
The biggest learning point for me was focusing on the **onboarding flow**. From the first moment, I ensured to guide visitors through flows that feel simple and intuitive. A great example of this is the demo contact form with registration CTA afterwards. Simplicity of creation also goes a long way by hiding more advanced settings until later steps of configuring the form. Last but not least, I received amazing feedback about the onboarding screen itself and the validation given by checking for first successful submission. Having the flow guide dedicated users is amazing, but the ability to skip it is equally necessary for those who just want to click through the app and see what features it offers.
Building my first production application using **shadcn** gave me confidence with this UI library and strengthened my understanding of UI/UX design. While I don't feel like using it on every project from now on, I am glad it's in my "toolbox" and I can execute with it whenever needed.
"Thanks to" laziness, I was tempted to skip the **landing page** due to time constraints of the weekend hackathon. While I did manage to set it up at the very end, it provided me with great perspective. I used to set up the landing page first to make sure the design was impressive enough to attract users. Doing it at the very end was less focused on design and more on its features. While usually I force copy on the homepage to fill spots, doing it last makes copywriting insanely easy and serves as great validation that the product is exactly what I wanted it to be. I will surely continue in this spirit, building the homepage at the very end.
## Future plans
Thanks to community feedback, I plan to add the following features in the future:
- File and folder drag & drop support in form submission
- Captcha support by multiple providers to prevent spam
- SMTP integration for sending emails
- Improved submission design and ability for export
- Starters with popular frameworks
- Written documentation and video content
---
### Project: HTTP Games
Description: Play games by sending REST API requests.
Demo: http-games.almostapps.eu
GitHub: Meldiron/http-games
Published: 2025-11-15
## Vision
Over the last couple of years, I have been interested in game development on-and-off. So far, the biggest challenge I faced was creating the sprites, animation, music, sound effects... I built a few games with [PICO-8](https://www.lexaloffle.com/pico-8.php) due to its limiting nature when it comes to those challenges, but games I built still didn't feel as professional as I would like.
It got me thinking. So, what parts of game making am I good at? Are there any genres that utilize only this aspect? I decided to try and build a programming game - game that you play by programming. Like [Screeps](https://screeps.com/)!

But that still has visuals... So, `Screeps alternative` without visuals?
After some searching I found even better inspiration, [Artifacts MMO](https://www.artifactsmmo.com/). Similarly to Screeps, it's played by programming a script, but this time it utilizes HTTP REST API directly.

Yet, I still wasn't 100% sure... Visualizing seems to be important for playing it, it got a huge wiki and many MMO RPG aspects that would take months to get right. So, `Artifacts MMO alternative`?
As silly as this approach sounds, finding an alternative and looking for its alternatives always led me to discover new ones. Until my sight landed on [SpaceTraders](https://spacetraders.io/)!

A game for programmers played entirely through HTTP requests. No visuals, no animations, no sounds. And so I found the genre I want to build, because it only utilized the skill I was most trained in, REST HTTP servers.
---
After trying SpaceTraders for some time, I quickly understood the most important part of such HTTP game* is documentation.
> * `HTTP Game`. At this point, I already knew what the project will be called.
I searched for go-to framework for building documentations, and there seems to be quite a few options. Most commonly mentioned were [Vuepress](https://vuepress.vuejs.org/) and its Vite alternative [VitePress](https://vitepress.vuejs.org/). From those two, I personally preferred Vitepress due to its more modern-looking design, and being framework agnostic.
There was also [Docusaurus](https://docusaurus.io/), [MkDocs](https://www.mkdocs.org/), [Docsify](https://docsify.js.org/), and many more, but they all lacked modern UI and UX I was hoping for.
By pure chance and luck, I came across [Fumadocs](https://fumadocs.dev/) which had exactly what I needed:
- Modern-looking UI similar to [shadcn/ui](https://ui.shadcn.com/)
- Mobile responsiveness with neat components
- Both light and dark mode
- Searching functionality
- `llms.txt` file generation
It was built with [Next.js](https://nextjs.org/) and [React](https://reactjs.org/), so technologies familiar to me, and similar to other docs frameworks, all content was written with Markdown. Specifically, [MDX](https://mdxjs.com/), a combination of Markdown for content and JSX for components.
## Technologies
- [Appwrite](https://appwrite.io/)
- Databases (TablesDB) for storing relational data and key-value cache
- Sites for deploying the webapp to the internet
- Functions for deploying the HTTP REST API
- [PHP](https://www.php.net/) with [Pint](https://laravel.com/docs/12.x/pint), [PHPStan](https://phpstan.org/) and [FrankenPHP](https://github.com/frankenphp/frankenphp)
- [Utopia.php](https://github.com/utopia-php) to build the HTTP server
- [Fumadocs](https://fumadocs.dev/) for modern-looking documentation
- [Hurl](https://hurl.dev/) for E2E tests of REST API
- AI. Bunch of AI. Like.. 80% AI
Most of my AI chatting and vibe-coding was made with [Zed AI](https://zed.ai/) and [Raycast AI](https://raycast.com/).
## Screens
Well, no visual, right? So not much to show...
Documentation page got a typical setup with left side to navigate pages, middle side for content, and right side for sections on current page. One neat feature Fumadocs offered was sidebar dropdown, allowing me to have separate docs sidebars for each game.

The docs are also nicely responsive on mobile device. I really liked detail of having circular progressbar as I am scrolling through sections of the documentation.

And as far as searching goes, indexes were created during the build, and so even without SSR, searching worked flawlessly. I also never required any Cloud services, which amazes me. Until now, I was always scared of searching capabilities and relied on [Elastic Search](https://www.elastic.co/search) or [Algolia](https://www.algolia.com/).

Since there isn't any UI to showcase, I highly recommend checking it out yourself! I tried the game myself using [Yaak](https://yaak.app/) HTTP client, and later automated it with [Bun](https://bun.sh/) script.


## Future plans
While developing the app, I left `TODOs` throughout the codebase. I wanted the prototype and first game to be finished very quickly, so I could give it to a few friends for feedback. And so I cut corners here and there, such as unoptimal looping, missing E2E tests, or ugly patches for loading environment variables. I wouldn't consider it a priority to resolve those, but I am very glad I noted them so I can get back to them easily.
One game is not enough! I will brainstorm more ideas of games that can be added, and motivate community to contribute.
Once the community grows enough, the addition of profiles, challenges, leaderboards and achievements is a must.
---
### Project: Rewrite_
Description: Practice typing by rewriting books as you read them.
Demo: rewrite.almostapps.eu
GitHub: meldiron/rewrite
Published: 2024-05-11
## Vision
The website [TypeLit](https://www.typelit.io/), which I used for a couple of weeks, required a paid subscription to add custom books. I upgraded, but soon realized the website was missing many features such as accent configuration, book page screenshots, gamification, and more.
When on a train, after finishing a page, TypeLit couldn't save my progress due to unstable internet connection, and there was no local backup. Instead of rewriting the page again, I decided to build my own application for it - Rewrite.
## Technologies
- Svelte and SvelteKit
- daisyUI with TailwindCSS
- Appwrite Databases
- Appwrite Functions using Node.JS
- Auth UI
- Google Cloud APIs
- EPUB.to APIs
## Screens
A simple homepage invites visitors to sign in before using the platform. To allow quick-play, users are welcome to create anonymous (guest) accounts with 1 click.

Authentication flows powered by [Auth UI](/projects/auth-ui), supporting many different authentication methods such as magic URL, phone OTP, email & password, OAuth, and more.

Logged-in users can visit a library containing books. They can pick from many pre-uploaded books, or upload their own `.epub` or `.pdf` books.

Once a user picks a book, they can select which page they want to rewrite. The table smartly filters out already finished pages, and sorts the rest in ascending order.

Rewriting a page of a book is the main view of this application. The user is given input and book's text. The goal is to rewrite the page without making mistakes - mistakes must be corrected.

If books include illustrations, users can also toggle page-view and see the book's original page design.

Completing a page grants users experience points, and invites them to continue to the next book's page.

Current level progress and experience remaining can also be found in a level modal.

Alongside leveling up the account, users can also maintain their streaks, and see how long they can go without missing a day of rewriting a page.

You may have noticed golden words in the page; those were words needed to finish a quest. Doing quests grants coins that can be used for cool abilities.

Users can also look at their profile statistics and complete set of achievements.

## Retrospective
The project gave me the confidence to develop further apps with daisyUI to support dark theme and theming out of the box. I also experienced the implementation of many gamification features, which can serve as a stepping stone to making a proper gamified self-improvement platform in the future.
While working on this project wasn't much of a learning experience, it became an amazing free alternative to its paid competitors, providing all the features that they do. Since I used it myself, I was able to improve and maintain it for over a year.
One of the most interesting problems I faced was creating a self-explanatory interface while books were being processed, so users wouldn't be left hanging, not knowing what to do next.
## Future plans
Since the app serves exact purpose it was meant to, I don't plan to update it in near future. With that said, following are some features what I can see as an improvement:
- Dependency-free e-book parser
- Streak calendar
- Rewards for levels
- Sharable public profile
- 0 Trust server validation
- Community events and goals
---
## Blog Posts
### Blog Post: I Found Perfect CMS after Years of Trial and Error
Published: 2025-04-02
Description: The best CMS for developers, freelancers, and small agencies
Reading Time: 14 min read minutes
As a freelancer, I always looked for ways to create admin panels for my clients. It gives them control, and makes me money. It was impossible to find something simple, free, and privacy-friendly. After a fair share of experience with dozens of systems, I finally found one that aligns with my expectations.
## Finding the Perfect CMS
Every CMS is useful. All of them exist because they have a target audience that is willing to pay, since it provides them value.
After years of searching, I created a set of rules that define the _"Perfect CMS"_ for static sites.
1. **Own my data**. Don't store my content, and don't become a dependency for my projects.
2. **Extendable**. Allow me to add custom components. Ideally open-sourced too, for ease of learning.
3. **Framework agnostic**. Don't tell me to use Angular. Integrate with any framework, and without a framework.
4. **Free**. Don't limit the amount of projects, clients, or features. Don't rely on paid users to keep the product alive.
5. **Self-hostable**. Don't require Cloud for authentication. Don't vendor lock-in access to CMS.
On top of those points, CMS must be simple to use, quick to implement, and nice-looking. Those all make it easier to convince clients they need a CMS.
### Choose Wisely, Choose Git-based CMS
> **Own my data**. Don't store my content, and don't become a dependency for my project.
The biggest difference between Git-based CMS and API-based CMS is complexity. Complexity can be beneficial to large blog platforms or e-commerce platforms, but that is not a typical project of freelancers. Much more common is a landing page, personal website, or community platform.
Simple websites don't benefit from API-based CMS, and get all the downsides. One must use an SDK, or send raw HTTP requests to fetch the data. One must implement caching to make their application quick again. And most importantly, one must have fallback behavior if CMS ever goes into downtime.
Git-based CMS solves all the pain points above. All data is stored in a file defined by you, whether it's JSON, YAML, TOML, or Markdown. Data is always accessible during the build step, so importing it directly gives you access with code auto-completion, and no delays when rendering the website. Data is stored directly as part of your source code, and doesn't create any direct link with the CMS that is used to edit them.
A significant downside of using Git-based CMS is the speed at which changes are reflected. Since a re-build is required after each change, it can take a couple of seconds (or up to a few minutes, depending on which framework you use) to apply changes on production. This can be a bit frustrating, but a typical freelancer's client doesn't mind. If one would honestly need changes instantly reflected on a website, they are at size when they benefit from API-based CMS anyway.
Thankfully, there are many Git-based CMS such as [Decap CMS](https://decapcms.org/), [TinaCMS](https://tina.io/), or [Crafter CMS](https://craftercms.com/).
### Component Library is Not Enough
> **Extendable**. Allow me to add custom components. Ideally open-sourced too, for ease of learning.
Basic components are enough for the first iteration of your CMS. As client's business grows, so do their demands. Coloring data based on importance, or adding map picker to visually represent location, may sooner or later become highly important for CMS users efficiency.
When that happens, your CMS should provide you with docs on how to extend it, instead of asking you to submit a feature request. Relying on maintainers is the worst outcome when it comes to custom components in CMS.
Many providers such as [Directus](https://directus.io/), [Appsmith](https://www.appsmith.com/), or [Budibase](https://budibase.com/) all have ways to fully customize the viewing and editing experience of each field.
### CMS Doesn't Live in My Application
> **Framework agnostic**. Don't tell me to use Angular. Integrate with any framework, and without a framework.
You likely experienced this before. You find an amazing UI library, you decide to use it in your next project, and moments later you realize it only supports Next.js. You find an amazing PDF library to generate great-looking invoices, only to realize it's for PHP. You find ...
Next.js and PHP are great. But it's my decision if they are great for the project I work on. If CMS provided support only for a specific framework, either you can't use it for every project you build, or you throw yourself into a framework you may not be familiar with, or even worse, it may not be the right choice for the job. CMS is there to assist you, not to throw logs under your feet.
[Payload](https://payloadcms.com/), a CMS powered by Next.js, or [Sveltia CMS](https://github.com/sveltia/sveltia-cms), a Decap CMS alternative using Svelte, are examples of CMS that I recommend to avoid until they become framework agnostic.
### Save Money to Make Money
> **Free**. Don't limit the amount of projects, clients, or features. Don't rely on paid users to keep the product alive.
Limits and free tiers are foundation stones of every commercial platform. It may remain open sourced, or a version 2.0 may be released with cloud-only support. It could remain free for unlimited projects, or it could change the limit to 3 projects over the weekend. It's also a matter of trust whether a Cloud doesn't change pricing from $5 per month to $15.
When picking a preferred CMS for simple sites, it should not limit the amount of projects, collaborators, fields, or rows. Allowing pricing to affect you eventually creates expenses, which lower your income from long-term clients.
[Sanity](https://www.sanity.io/), [Contentful](https://www.contentful.com/), and [Hygraph](https://hygraph.com/) are a few examples. Very often a CMS has a pricing page, but is also open-source, and has docs on how to self-host it for free. A great example of that is [Directus](https://directus.io/), and there is no need to avoid those CMS.
### Authentication, a Bait for Headless CMS
> **Self-hostable**. Don't require Cloud for authentication. Don't vendor lock-in access to CMS.
Git-based CMS often provide Cloud authentication to simplify process of setting it up. This is perfect for development, and initial integration to ensure everything works well together. As soon as application goes to production, it's a great risk to keep using the Cloud offering.
Primary problem is the fact you are authorizing someone's else GitHub application, not yours. This means maintainers of the Cloud offering of the CMS can see contents of your repository, do changes, and possibly even more. This creates potential for security risks, makes app less likely to comply with strict privacy regulations, and can create helpless situation during authentication gateway downtimes. Such Cloud authentication can also sunset at any moment.
## Honorable Mention: Outstatic
Until now, my favourite CMS was [Outstatic](https://outstatic.com/), a CMS for Next.js applications. It's fully open source project that can be self-hosted, and focuses on keeping your data inside your git repository. It's simple-looking interface is amazing for any small CMS, and AI integration for text generation can be a great plus for some projects.
Downside of Outstatic is limited collaboration features, since it doesn't come with database, so only authentication method is GitHub OAuth. This can be limiting, as explaining to clients what GitHub is and why they need the account can be hard. Additionally, it's SDKs tightly couple you to use Next.js.
## Why Pages CMS is the Best CMS
Considering all of the above, the best CMS is [Pages CMS](https://pagescms.org/), a modern Git-based CMS with a focus on static sites. With a single configuration file, in matter of minutes, it allows you to manage your content and its media files through an intuitive UI. Pages CMS support all collaboration features you or your client might need including MagicURL login, history of changes, and more. It comes with a responsive design to let your client do changes from their phone.
Pages CMS keeps all your contents inside your repository, and only requires a single configuration file to be set up. What's even more, it's framework agnostic and can be used with Astro, Next.js, SvelteKit, or any other framework. Because of the nature of Pages CMS, it doesn't integrate with a framework, and only looks at files containing your data such as JSON, YAML, Markdown, or TOML.
It supports 9 file formats and 13 built-in field types. While this is more than enough, Pages CMS can be forked, self-hosted, and extended with custom components. Doing this requires basic understanding of React, but detailed documentation and existing field types serve as great starting points.
The entirety of Pages CMS is free and open sourced, including its Cloud platform. Cloud offering has no limits on the amount of collaborators, projects, or content. Developers of Pages CMS even announced `Free forever` as part of their [1.0 release notes](https://github.com/pages-cms/pages-cms/discussions/128).
## Let's Integrate with Pages CMS
As with any CMS I come across, I try to use it with an application. This time I decided to use [Astrolink](https://github.com/alamguardin/Astrolink), a minimal alternative to [Linktree](https://linktr.ee/). You can check it out on a [demo page](https://astrolink.vercel.app/), or see it's details in [Astro theme catalog](https://astro.build/themes/details/astrolink-template-to-share-about-yourself/).

## Prepare the Website
Initial observation shows Astrolink already sources all of the page data from a single file [src/data/user.json](https://github.com/alamguardin/Astrolink/blob/main/src/data/user.json). This means, Pages CMS will only need access to this file, and I don't need to make any adjustments to source code.
> If you want to use Pages CMS with your project, and you have your data inside components or JavaScript/TypeScript files, I highly recommend moving data to JSON files first. Many frameworks have ability to import JSON files directly into components. Those that don't can store JSON files in public assets folder, and be loaded with a `fetch()` request.

Getting Astrolink live was matter of a few very simple steps:
1. Fork GitHub repository
2. Deploy website to Cloud ([Vercel](https://vercel.app/), [Netlify](https://www.netlify.com/), or others)
3. Connect custom domain using DNS
Once deployed, it was time to configure Pages CMS.
## Connect with Pages CMS
To get started with [Pages CMS](https://pagescms.org/), I visited their [Cloud platform](https://app.pagescms.org/), and signed in with GitHub. Through this process I authorized with Pages CMS OAuth application, and gave their GitHub application access to my repositories, including read and write access.
> Pages CMS can be self-hosted **very easily**, and they have a step-by-step guide on how to do that. I highly recommend self-hosting Pages CMS and authorizing your own GitHub application for security and privacy reasons.
Once my GitHub was connected, I saw my newly forked Astrolink repository in the list of Pages CMS projects.
The project was not ready to be used yet, because Pages CMS needs a `.pages.yml` configuration file that will hold all the Pages CMS configuration. Frankly, I didn't need to know that. A nice-looking error page explained it all, and provided a button to create an empty configuration file to continue.
Afterwards, I spent a couple of minutes reading [Pages CMS documentation](https://pagescms.org/docs/), more specifically [Configuration](https://pagescms.org/docs/configuration/) and [Examples](https://pagescms.org/docs/examples/) pages.
While fields in YAML file were not any standard knowledge developers usually have, it was very easy to understand. I am nowhere near knowing the exact schema by heart, but I already feel confident setting up a configuration file for any kind of data thanks to its highlighting, and schema validation directly in Pages CMS.
Below you can find configuration file I used for the Astrolink project:
```yaml
content:
- name: user
label: User
path: src/data/user.json
type: file
fields:
- name: name
type: string
- name: profession
type: string
- name: links
label: Links
path: src/data/user.json
type: file
fields:
- name: links
type: object
list: true
fields:
- name: title
type: string
- name: description
type: string
- name: icon
description: Icon names can be found at www.remixicon.com
type: string
- name: url
type: string
pattern: ^(https?:\/\/)?(www\.)?[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}(\/[^\s]*)?$
```
As I saved the configuration file, new options appeared in the left side menu. Inside, I found my schema applied in a simple interface of inputs and buttons. What's even more interesting, all data was already loaded, indicating the connection to data stored in `src/data/user.json` was successful.

I updated a few texts, and saved the changes. A commit was created automatically, pushed, and a Vercel deployment started. After couple of seconds, I refreshed my website and changes I made were live.
## Collaboration with Client
Since the main purpose of CMS is to provide the client with a simple interface to update content on their static site, I decided to try the full experience with Pages CMS.
In collaboration settings, I added a friend of mine to the project through email invitation. They received the invitation in their inbox, and followed steps provided. An amazing feature presented itself when I noticed the sign-in page supports passwordless email login alongside GitHub OAuth. I can't stress enough how important this is since clients almost never have GitHub accounts.
After a passwordless login using Magic URL, my friend already saw the Astrolink project in their list, and started editing content. No setup was needed from his side.
Finally, yet another feature amazed me. When my friend started making changes, I saw it all on the right side of Pages CMS, which served as a history of changes. If a client **ever** does a change that breaks the website, as a developer I can easily see what the change was, when, and within a few clicks I can rollback the changes to make the website stable again. Moments like this make business relations more stable and lucrative.

Both website and CMS are now live! Within less than hour of finding out about Pages CMS, I was able to deploy my first site with CMS integrated. You can visit my [Astrolink website](https://matejbaco.vercel.app/), and I invite you to make yours to try out Pages CMS. Additionally, I recommend you to make picture URL on the website configurable, to gain further experience with Pages CMS. For those looking for challenge, Pages CMS supports media, so you can switch from picture URL to proper drag&drop file input.
## Pages CMS in Production
As mentioned above, Pages CMS requires read and write access to your repository, which can be scary for multiple reasons:
- Security: Pages CMS Cloud has full access to your repository, possibly deleting the entire project.
- Privacy: Pages CMS Cloud has permission to see the source code of your application.
- Billing: Pages CMS Cloud can become paid. They promised it won't, but it can.
While self-hosting doesn't fully address security, it surely makes it more secure. Bugs in code can still cause problems, but the risk is much more manageable.
Setting up self-hosted Pages CMS looks like complex task since it has many moving parts, but their docs are **amazing** and provide follow-along instructions to set everything up. The entire setup was made with developers in mind, as it recommends [Resend](https://resend.com/) and [Turso](https://turso.tech/), both Cloud applications that provide free tiers.
What caught me by surprise was how easy it is to add custom components. From my past experience building CMS for clients, I know simple component changes can make a big difference. _For example, simply coloring a date red or green depending on package shipping status can avoid delayed deliveries._ Not only was the process of adding custom components documented, it also linked to over a dozen existing components providing a starting point.
> If you are interested to gain confidence with custom components, I recommend to build component that stores latitude and longitude of location on a map, and provide interface using [Google Maps](https://developers.google.com/maps), [OpenStreetMap](https://www.openstreetmap.org/), or similar map provider.
If you use Pages CMS in production, and successfully self-hosted the application, it's the best time to consider [sponsoring Pages CMS](https://github.com/sponsors/hunvreus). Engineers behind this amazing open-source project are active, and providing them with financial support allows them to develop new features, which can benefit you in the long run.
## Conclusion
I am finally at peace. No more searching for a better CMS. Pages CMS is my go-to, if I ever need one for sites I build. It provides a nice-looking and simple interface that's quick and easy to setup. It's free, open-source, self-hostable, framework-agnostic, and doesn't lack collaboration features. What more should I ask for.
If you don't share my passion for Pages CMS, take inspiration from my criteria for the perfect CMS. Many of them make sense in every situation, and help you find a solution that is long-term, and sustainable. If it wasn't clear already, every static site deserves a CMS.
---
### Blog Post: Go, high-performance runtime and SDK
Published: 2024-08-22
Description: Go runtime is now supported, and we released a Go SDK.
Reading Time: 1 min read minutes
This is an external article. Visit [Appwrite blog](https://appwrite.io/blog/post/announcing-go-support) to read this article.
---
### Blog Post: Faster and safer Functions development in your local environment
Published: 2024-08-19
Description: You can now develop and test functions on your local machine.
Reading Time: 1 min read minutes
This is an external article. Visit [Appwrite blog](https://appwrite.io/blog/post/announcing-local-development) to read this article.
---
### Blog Post: Measuring Appwrite's Go runtime performance
Published: 2024-08-08
Description: Benchmark of our new Go runtime in comparison with other runtimes.
Reading Time: 1 min read minutes
This is an external article. Visit [Appwrite blog](https://appwrite.io/blog/post/go-function-benchmarks) to read this article.
---
### Blog Post: Integrate any database into your Appwrite project
Published: 2024-05-24
Description: With databases it's always "it depends", but we got the answers.
Reading Time: 1 min read minutes
This is an external article. Visit [Appwrite blog](https://appwrite.io/blog/post/integrate-sql-nosql-vector-graph-or-any-database-into-your-appwrite-project) to read this article.
---
### Blog Post: Biometric authentication with Passkeys
Published: 2024-03-09
Description: Authenticate users with their what they are.
Reading Time: 7 min read minutes
Password-based authentication is the most common form of authentication, but it is not the most secure. Ideally, everyone is using a password manager to have a unique and strong password on every website, and everyone protects their account with multi-factor authentication. That's not a reality, sadly. While the website can enforce some best practices for password security, password reusing - a leading problem of internet security - not all can be prevented by developers.
## Introduction and benefits
Passkeys provide an authentication method that does not require users to remember a password. Instead, to authenticate, a combination of owning a device with a private key and authorizing yourself with fingerprint, face, or voice is needed. Passkeys use public key cryptography technology, which provides security that is incomparable to password-based authentication. If a server or a database is ever compromised, the user's passkeys remain secure since there is no secret stored on the server. This makes server breaches ineffective to passkey authentication. The nature of implementation also makes phishing attacks impossible since a passkey can only be used on the website on which registration happened. Your users will no longer be tricked into signing in to an attacker's website.
Using passkeys instead of password-based authentication can lead to faster and more secure sign-ins, reduce the cost of multi-factor authentication such as SMS or e-mail, and provide a faster sign-up process. Passkeys can also be used to safely access your account on a friend's device or in a public library, thanks to passkey QR codes. This gives convenience that other sign-in methods, such as MagicURL or OAuth2, do not have.

## Step by step implementation
Implementing passkey involves multiple steps to keep the entire process secure and reliable. The following technologies will be used in this demo:
- [SimpleWebAuthn](https://simplewebauthn.dev/) for passkey implementation
- [Alpine.js](https://alpinejs.dev/) for reactive frontend
- [Node.js](https://nodejs.org/en) for custom backend endpoints
- [Appwrite](https://appwrite.io/) for user management, databases, and serverless functions

The entire source code can be found on [GitHub](https://github.com/Meldiron/authenticate-with-passkey). I recommend checking it out if you are planning on implementing passkey in your project. Code snippets in the article are simplified to showcase the basics of the implementation, while source code on GitHub covers all edge cases. Such edge cases are, for example, input validation, challenge cleanup, or improved logging.
### Registration with Passkey
The registration form consists of two very simple components, an e-mail and a submit button.
```html