Open-source project
netless-io/flat-server avatar
netless-io/flat-server

flat-server is the backend that signs tokens and schedules rooms for Agora Flat

A Node.js server for the Agora Flat open source classroom.

684 stars276 forksTypeScriptMIT

At a glance

What is it?
The Flat classroom is a client, and flat-server is the Node.js and TypeScript service behind it: it logs users in through WeChat, GitHub and Google, schedules ordinary and periodic rooms, issues signing tokens for the whiteboard, RTC and RTM, and stores recordings and courseware. It is a lot of YAML, and it is tied to Agora's cloud services.
Who is it for?
Adopt flat-server when you are running the Agora Flat client and need the matching backend, with Agora credentials for whiteboard, RTC and RTM tokens, and with a willingness to maintain a service whose configuration is a large YAML file. Do not adopt it to build a generic classroom, because the Agora dependencies run through it, and do not expect a supported release channel.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 73 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 24, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The server behind the Flat client

The project describes itself as a `Node.js` server for the Agora Flat open source classroom, and the README points at the front end repository it belongs to. The division of labour matters if you are evaluating it: Flat is the classroom application, and this repository is the service that authenticates people, hands out the tokens the classroom's media components need, and stores the media itself.

The feature list is the clearest statement of scope. Login is offered through WeChat, GitHub and Google. There is room scheduling, for both ordinary rooms and periodic rooms, so a recurring class can be created without anyone pressing a button each week. The server signs tokens for the Whiteboard, for real-time video and audio chat, which the list calls RTC, and for real-time messaging, which it calls RTM. It handles cloud recording and replaying, and it provides Cloud Storage for multi-media courseware.

So the service is a classroom backend in the full sense: identity, schedule, media credentials, archive and files. The primary language is TypeScript, the licence is MIT, the default branch is `main`, the repository is not archived, and the last push was on 2026-08-13. There are no GitHub releases published, which matters when you get to the question of how you would pin a deployment.

Two processes to run it locally

The develop section is short and assumes you will read the configuration reference afterwards.

The installation is a single command:

shell
yarn install --frozen-lockfile

Then you create the file `config/development.local.yaml` and add environment variables following the `config/defaults.yaml` format, which is the pattern the reference section documents.

To run the project you execute at the project root:

shell
   yarn run start

That starts the development server, which by the look of the package scripts is webpack in watch mode with `NODE_ENV=development` and the development webpack config. Then you open another terminal and execute at project root:

shell
   node ./dist/index.js

The reason there are two steps is that the build and the run are separate. `yarn run start` compiles and watches, and the second command starts the compiled entry point from `dist`. The README tells you what success looks like: you should see `ready on http://0.0.0.0:80` if everything is OK. Port 80, with the server listening on all interfaces, is the default in the configuration and it is a detail worth noticing before you start a local instance, since that is a privileged port.

For deployment the README is refreshingly relaxed, saying deployment is not needed for local development, and that if you want to bring the server up online, deploy anyway you like. It also warns that if you have deployed this project before, you should see the migration documentation in `scripts/migration/README.md`.

The configuration file is the real interface

The environment variables reference is the largest part of the README, and reading it is how you learn what the service actually depends on. The first block covers the server, Redis, MySQL and JWT:

yaml
server:
    # Server port
    port: 80

redis:
    # Redis host
    host:
    # Redis port
    port:
    # Redis username
    username:
    # Redis password
    password:
    # Redis db name
    db:
    # Redis-based implementation of the deferred queue db name
    queueDB:

mysql:
    # MySQL host
    host:
    # MySQL port
    port:
    # MySQL username
    username:
    # MySQL password
    password:
    # MySQL database
    db:

jwt:
    # JWT secret
    secret:
    # JWT crypto algorithms, see: https://github.com/auth0/node-jsonwebtoken/tree/d71e383862fc735991fd2e759181480f066bf138#algorithms-supported
    algorithms:

So there are three backing stores before you get to any cloud service: Redis for caching and for a deferred queue implemented on Redis, MySQL for the relational data, and a JWT secret with an explicit list of allowed algorithms. The `website` value points at the front end address, and the `log` section has a `pathname` and a `filename` that are templates, `{{PROJECT_DIR}}/logs` and `{{DAY_DATE}}`, so log files roll by date inside a project directory.

The `cloud_storage` block then sets the limits for courseware uploads: a concurrency of 3, a single file size of 524288000 bytes, a total per user of 2147483648 bytes, an upload path prefix of `cloud-storage`, and an allow list of extensions covering presentations, documents, images, audio and video. The `user` and `oauth` blocks apply the same pattern to avatars and to the logos uploaded for the login buttons, with a 5M size limit and their own extension lists.

Login providers, and an SMS path that is on by default

The `login` section is the most detailed part of the configuration, and it reveals how much of this service is aimed at a specific deployment.

WeChat login has a web and a mobile variant, each with an enable flag, an App ID and an App Secret, and both are disabled by default. GitHub login takes an enable flag, a client ID and a client secret. Google login takes the same plus a redirect URI. Apple login has only an enable flag. And Agora login has a client ID and a client secret with the comment that it is not open to the public at the moment.

The SMS path is the one that is switched on. `login.sms.enable` is true, with a `force` flag for forcing phone binding, and a `test_users` list of phone and code pairs. The comment on the test users is explicit that the entry is only valid if the server environment is dev, and that you need to run MySQL yourself to insert the user, with the two INSERT statements for `user_phone` and `users` given right there.

The SMS provider is Aliyun, and the configuration is split by region: `chinese_mainland`, then `hmt` for the Hong Kong, Macao and Taiwan regions, and then `global`, each with an access id, an access secret, a template code and a sign name. So an operator has to procure SMS credentials per region before any phone login works, and the default configuration ships with the mainland, regional and global blocks all empty.

Agora credentials split three ways, and they are not optional

The `agora` block is where the service meets the cloud, and it is split into three groups that serve three different features.

`agora.app` holds the Agora App ID and the App Certificate, and the comment ties it to RTC and RTM. Those two values are what the signing token feature consumes, so without them the classroom has no media credentials and the service is decorative.

`agora.restful` holds a RESTful ID and secret, and the comment ties it to classroom replaying, with a link to the Agora documentation on RESTful authentication for the cloud recording question.

`agora.oss` is for storing RTC Cloud Recording media files, with an access id, an access secret, a vendor, a region, a bucket, a folder and a prefix, and the comment gives the path through the API: Cloud Recording Start, then Schema, then clientRequest, then storageConfig. There is also a separate note about the video screenshot service.

That three-way split is the design to understand before you adopt the project. Token signing, replay and media storage are three separate integrations with three separate sets of credentials, and the recording path additionally needs object storage that the service can write into and read from.

The `cloud_storage` block sits next to these and is a different thing: it is the classroom's own file area for courseware, with the limits already described, separate from the recording bucket that Agora writes into.

Room scheduling, recording and the limits around both

The two scheduling modes are listed as ordinary rooms and periodic rooms, and everything else in the feature list hangs off them.

Cloud recording and replaying is the archiving side of a classroom: the meeting is recorded to the cloud and can be played back later, and the credentials for that path are the `agora.restful` pair plus the object storage settings. The `agora.oss` comment even shows the exact place in the recording start request where the storage configuration goes, which is the kind of detail you normally have to reverse engineer from an API reference.

Cloud Storage for multi-media courseware is the other half, and it is the one with visible limits, all of them in the configuration and all of them in bytes. Uploads run at most 3 at a time. A single file is capped at 524288000 bytes, which the comment labels as 500M. A user's total is capped at 2147483648 bytes, labelled 2G. The path prefix is `cloud-storage`, with the comment noting no leading slash or suffix, and the allowed extensions are the ones a teacher would actually use: `ppt`, `pptx`, `doc`, `docx`, `pdf`, `png`, `jpg`, `jpeg`, `gif`, `mp3`, `mp4`.

Those numbers are the kind of thing that decides whether the project fits, and they are defaults you can change. What you cannot change from the YAML is the fact that a periodic room still needs a working Agora account behind it, since the class is a real-time media session rather than a document exchange.

How the project is built, packaged and tested

The repository is a webpack and TypeScript service with a test suite and a container image.

The package manifest shows the scripts and the test stack:

json
{
  "name": "flat-server",
  "version": "1.0.0",
  "main": "dist/index.js",
  "license": "MIT",
  "scripts": {
    "start": "cross-env NODE_ENV=development webpack --watch --progress --config webpack/webpack.dev.js",
    "build": "cross-env NODE_ENV=production webpack --progress --config webpack/webpack.prod.js",
    "test:sync:orm": "ts-node -r dotenv-flow/config scripts/sync-orm.ts -- --default-node-env=test --dotenv-flow-path=config",
    "test:local": "./scripts/prepare-test-env.sh && yarn run test:sync:orm && yarn run test",
    "test": "c8 --reporter=html ava",
    "lint": "lint-staged"
  },

The test setup is worth a note. `test:local` prepares a test environment with a shell script, synchronises the ORM, then runs the suite, and the suite itself is `ava` under `c8` with an HTML reporter. There is a dedicated script for synchronising the ORM into a test database, which tells you the schema is managed by migrations that a test run has to apply before the tests mean anything.

The container image is a multi-stage build on `node:18.4.0`, with a builder stage that installs with a frozen lock file and runs the production build, a separate stage for production dependencies, and a final stage that copies `dist` and `node_modules` and exposes port 80 with `node dist/index.js` as the entry point. The repository also ships a `helm/` directory, so a Kubernetes deployment is the path of least resistance, and the CI configuration is in `.gitlab-ci.yml`.

Around that sit the usual conventions: `husky/` with `.lintstagedrc` for commit hooks, `.eslintrc.js` with the prettier and import plugins, `.commitlintrc.js`, `ava.config.cjs`, `.babelrc`, a `.node-version` file, `typings/`, and a second README in Chinese at `README-zh.md`.

Editorial conclusion

Adopt flat-server when you are running the Agora Flat client and need the matching backend, with Agora credentials for whiteboard, RTC and RTM tokens, and with a willingness to maintain a service whose configuration is a large YAML file. Do not adopt it to build a generic classroom, because the Agora dependencies run through it, and do not expect a supported release channel. Verify first that your `agora.app.id` and `agora.app.certificate` are set, since token signing fails without them, that Redis and MySQL are reachable at the configured hosts, that the `website` value points at the front end you actually serve, and that the copy you take has a migration path, given the migration documentation under `scripts/migration/` and a last push on 2026-08-13.

Frequently asked questions

How do I run flat-server locally?

Install the dependencies with `yarn install --frozen-lockfile`, create `config/development.local.yaml` following the `config/defaults.yaml` format, run `yarn run start` in one terminal and `node ./dist/index.js` in another. The README says you should see `ready on http://0.0.0.0:80` if everything is OK.

What does flat-server need in its configuration?

At minimum a server port, a Redis instance including a separate queueDB for the deferred queue, a MySQL database, and a JWT secret with the allowed algorithms. On top of that it needs the Agora App ID and certificate for RTC and RTM tokens, a RESTful id and secret for replaying, and object storage settings if you want recordings kept.

Which login methods does flat-server support?

WeChat, GitHub, Google, Apple, Agora, and SMS. The first four are disabled by default and take an app id or client id with a secret, while the SMS path is enabled by default and uses Aliyun templates configured separately for the Chinese mainland, the Hong Kong, Macao and Taiwan regions, and the rest of the world.

How are the tests run in flat-server?

The `test:local` script prepares a test environment with `scripts/prepare-test-env.sh`, synchronises the ORM with `test:sync:orm`, and then runs `test`, which is `c8 --reporter=html ava`. The package is built with webpack, with separate development and production configs in the webpack directory.

Official sources

  1. Issues
  2. License: MIT
  3. netless-io/flat-server on GitHub
  4. README
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.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/netless-io-flat-server.svg)](https://hysenlabs.com/projects/netless-io-flat-server)

Was this useful?