Scholarsome is a self-hosted flashcard system without spaced repetition
Web-based interactive flashcard learning software
At a glance
- What is it?
- Scholarsome is an AGPL licensed web flashcard system built with NestJS, Angular, Prisma and Nx, meant to be run on your own server with MariaDB and Redis. It imports and exports to Anki, Quizlet and CSV, and it lists spaced repetition scheduling under features coming soon, which is the one capability that defines the category.
- Who is it for?
- Scholarsome makes sense for one specific person: someone who wants a study web app on infrastructure they control, who is comfortable running MariaDB and Redis alongside it, and who does not yet need an algorithm to decide when to review a card. For anyone else, the gap between the pitch and the feature list is the deciding factor, because a drop-in replacement for a proprietary flashcard service is mostly the scheduling, and the scheduling is not here.
- Can I use it commercially?
- Yes, with strict conditions. AGPL-3.0 is a network copyleft licence: if people use a modified version over a network, for example as a hosted service, you must offer them its source code under the same licence.
- Is it still maintained?
- Yes. The repository last received commits 101 days ago.
- What is it written in?
- Mainly TypeScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 2, 2026, and from our analysis. They are not legal advice.
Editorial analysis
Spaced repetition is on the coming soon list
The feature list splits into two halves and the more important half is empty. What ships: create your own study sets, LaTeX for maths, share sets with other users, import from and export to Anki, Quizlet and CSV, study in multiple modes described as mimicking real world flashcards, build quizzes as fill-in-the-blank, true or false and multiple choice, edit sets, and change set visibility.
What does not ship, under features coming soon: interactive study games, an implementation of a spaced repetition system, persistence of quiz results, sharing of editing permissions with other users, an improved profile page, and a user-accessible API.
That first item on the second list is the whole story. A scheduling algorithm is what separates a flashcard system from a stack of index cards, and without it the study modes can only simulate the physical experience of flipping cards. Quiz results that are not persisted mean the app cannot know what you have already seen, which is the input any scheduler would need. The design philosophy section is candid about this, saying the project is far from complete and that it ships fast and early for feedback, and the stated approach is to add features in order from simplest to most complex. Spaced repetition is the most complex item on the list and it is still waiting.
The compose file pulls a latest image, and the newest release is from 2024
The compose file does not build anything. The application service names a prebuilt image, `hwgilbert16/scholarsome:latest`, and the two backing services name `redis:latest` and `mariadb:latest`. There is no `build:` key anywhere in the three services, so bringing the stack up means pulling whatever the registry currently serves under a floating tag.
The version history explains why that matters. Published releases are v1.0.10 on 2023-12-24, v1.1.0 on 2024-01-18 and v1.2.0 on 2024-03-13, and the root manifest still reads 1.2.0. The last push to the default branch, which is `develop` rather than `main`, is dated 2026-06-24. So the newest tagged build is more than two years older than the newest commit, and neither of them is what a compose install selects.
Two consequences follow. Nobody can say which code a running instance has, because the image carries no version, and rolling back is a matter of finding an older tag by date. And the manifest version being stuck at 1.2.0 means the codebase in the branch has drifted away from the published line without a version bump to mark it.
One environment variable is three credentials
The compose file passes a single variable, `DATABASE_PASSWORD`, into three separate places. It becomes the MariaDB user's password, it is interpolated into the application's connection string as `mysql://scholarsome:${DATABASE_PASSWORD}@mariadb:3306/scholarsome`, and it becomes the Redis password, which is handed both to the application as `REDIS_PASSWORD` and to the Redis server itself through `--requirepass` in its start command.
So one secret in one env file is simultaneously a SQL credential, a DSN credential and a cache credential, across two services with different data and different exposure. Any rotation means changing it in one place and restarting both containers together, and any leak of that variable discloses all three at once. The Redis username is set to an empty string, which leaves the password as the only thing standing between the internal network and the cache.
Two defaults in the same file are better chosen. MariaDB is started with a randomised root password and root restricted to localhost, and both databases are declared with `expose` rather than `ports`, so neither publishes a host port. Only the application port is published, and both data services sit behind it on the internal network.
TLS is wired into the app and commented out in the compose file
The application is configured for TLS. Two environment variables, `SSL_KEY_BASE64` and `SSL_CERT_BASE64`, carry the key and the certificate as base64 so a certificate can be injected without baking it into an image. That is a workable pattern and it is the only TLS configuration the file offers.
The port mapping is not enabled. The application service publishes `${HTTP_PORT}:${HTTP_PORT}` and the next line, the one that would publish an SSL port on 8443, is commented out. So a default deployment comes up on plain HTTP with a commented hint that TLS is possible, and the variables sit there waiting.
The project's own homepage field makes the same point from the other direction: it is recorded as a plain http address, while the About section describes the product as keeping user data secure locally and as an open alternative to services that paywall core features. The self-hosted path is the security story, and on the shipped compose file that story starts with an uncommented line.
The design philosophy also settles what local means. It argues that having no syncing between devices removes a confusing topic, because data is stored, accessed and edited from one central server. That is a real simplification, and it is also why the stack needs a database and a cache rather than anything on the user's disk.
The production image carries the source tree and builds twice
The Dockerfile is a two stage build on an Alpine base, and the second stage does more copying than it needs to. The builder installs a compiler toolchain, runs an install with development dependencies omitted and scripts ignored, and then explicitly rebuilds the two native modules:
RUN npm rebuild bcrypt --build-from-source
RUN npm rebuild sharp --build-from-sourceThe install already had scripts disabled, so those two rebuilds are the only point at which bcrypt and sharp are compiled. They are compiled from source on Alpine, where the prebuilt binaries would not have matched, which is the right call and is also why a compiler is installed in the first place.
The runtime stage then runs `COPY . .`, copying the entire repository into the image, and after that copies `dist` and `node_modules` from the builder on top of it. The result is an image containing the built output, the dependency tree and the full source, rather than the output alone. For a public image on a public registry that is a larger attack surface and a larger download than the application needs.
The entry command runs the Node server rather than a process manager, so the alternative process manager script in the manifest is not what the image uses.
Every container start runs the database migrations
The image's default command chains two operations: `npm run migrate && node dist/apps/api/main.js`. The migrate script is `npx prisma migrate deploy`, so every start of the container applies pending schema migrations before the server binds. For a single replica that is convenient and for a restart after a failed deploy it is exactly the behaviour you want.
For more than one replica it is not. Concurrent migration deploys against the same MariaDB instance are a race, and nothing in the compose file limits the application service to a single container. The alternative script in the manifest, `serve:pm2`, does the same migrate then start step under a process manager, and it exists in the repository without being the image's entry point.
One more asymmetry is worth noting. The development serve scripts mostly bind the default interface, while `start:front` is the only one that passes `--host=0.0.0.0`. That matters for anyone running the front end in a container during development, and it means the published compose port mapping and the default dev binding are not describing the same thing.
The test script reaches the API and nothing else
`npm test` runs `npx nx test api`. That is the whole command, so the NestJS API is the only project covered by the test script, while the build script targets three projects, `front`, `api` and `docs`. A contributor running the documented test command gets no signal on the Angular front end or on the documentation site.
The rest of the quality tooling is more thorough than that. Lint runs across all projects with fixes applied, and the root carries an ESLint configuration with its own ignore file, a Prettier configuration and ignore file, an editor config, a commitlint configuration and a husky directory, with `prepare` set to husky so the hooks install on install. Three example environment files sit at the root, one each for a compose deployment, local development and a plain Docker run, which is a sign the deployment shapes were thought about separately.
The repository is an Nx workspace with an `apps/` directory for the three applications, `libs/` for shared code, a `prisma/` directory, a `tools/` directory and a script that decorates the Angular CLI. The share text at the top of the README still tags the project with a bootstrap hashtag, a leftover from before the front end moved to Angular.
Two pinned frameworks and a storage switch
The manifest pins two frameworks more tightly than the rest. Every Angular package sits on the 16.2 line, with the compiler and the build tooling included. The documentation framework is different: Docusaurus is pinned to an exact 2.1.0, with no caret and no tilde, while its preset is pinned the same way. Everything else in the visible dependency block uses a range, so the docs site is the one part of the build frozen to a single old release.
Storage is chosen by one variable. `STORAGE_TYPE` selects the backend, and the compose file configures both at once: a local directory under `/data` and six S3 variables covering the endpoint, access key, secret key, region and bucket, alongside an AWS SDK client pinned on a 3.x range. So the same deployment supports object storage or a local volume with no other change, which is the right shape for a self-hosted project, though it also means six S3 settings sit in every environment file whether or not object storage is used.
The licence is AGPL, declared in the manifest as AGPL-3.0-or-later, with a security policy and a contributor covenant linked from the README. The gaps in the feature list, not the architecture, are what to weigh before adopting it.
Editorial conclusion
Scholarsome makes sense for one specific person: someone who wants a study web app on infrastructure they control, who is comfortable running MariaDB and Redis alongside it, and who does not yet need an algorithm to decide when to review a card. For anyone else, the gap between the pitch and the feature list is the deciding factor, because a drop-in replacement for a proprietary flashcard service is mostly the scheduling, and the scheduling is not here. Before deploying, pin a version rather than tracking the latest tag, since the newest published release is from March 2024 while the default branch has been pushed as recently as June 2026. Then rotate the credentials deliberately, because one environment variable in the compose file serves as the database password, the connection string credential and the Redis password, and there is no version number on the image you would be pulling to fix it.
Frequently asked questions
What is Scholarsome?
It is a web based open source flashcard studying system licensed under AGPL and built with NestJS, Angular, Prisma and Nx. It is intended to be self hosted, with a compose file that runs the application alongside MariaDB and Redis.
Does Scholarsome implement spaced repetition?
Not yet. The README lists a spaced repetition system implementation under features coming soon, alongside interactive study games and persistence of quiz results. The study modes it does ship are described as mimicking real world flashcards.
How do I install Scholarsome?
The repository ships a compose file and a Dockerfile. The compose file pulls the prebuilt image hwgilbert16/scholarsome:latest rather than building from source, so an install runs an image the registry supplies under a floating tag.
What database does Scholarsome use?
MariaDB, with Redis alongside it. One variable, DATABASE_PASSWORD, is passed as the MariaDB user password, inside the DATABASE_URL connection string, and as the Redis password and requirepass, so a single secret covers three credentials.
Which flashcard formats can Scholarsome import and export?
Anki, Quizlet and CSV files, in both directions. Sets can also be shared with other users and their visibility changed, though sharing editing permissions is listed as a future feature.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/hwgilbert16-scholarsome)