nest-admin: a NestJS backend with button-level permissions and migrations on every boot
NestJS CRUD for RESTful API 使用 NestJS + Mysql + Typeorm + Redis + JWT + Swagger 企业中后台管理系统项目RBAC权限管理(细粒度到按钮)、实现单点登录等...
At a glance
- What is it?
- nest-admin is a NestJS and TypeScript administrative backend with role-based permissions down to the button, single sign-on, generated API documentation, and a Vue interface that lives in a separate repository. The details worth reading before you adopt it are not in the feature list: a shared initial password for every user you create, a database root password set to the application password, and a container entrypoint that runs your migrations every time it starts.
- Who is it for?
- nest-admin is a solid starting point if you want a typed, layered API with permissions enforced in the service layer rather than only in the interface, and the choice of an ORM with real migrations plus generated API documentation is the reason to look at it rather than a hand-rolled controller. The interface is a separate repository, so budget for two codebases and two release trains rather than one.
- 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?
- Activity is slowing. The repository last received commits 6 months 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 September 28, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The backend is here, and the interface you actually look at is in another repository
The first structural fact is that this is not a full stack project. The repository is the backend, and the readme's own header points to a separate project for the front end, built on Vue 3 and a component library. Two consequences follow immediately. You need both codebases to have a working application, and they version independently, so the interface can be ahead of or behind the API it calls. The second consequence is where the interesting documentation is, because this readme is entirely about the API and its first substantive content is a set of demo links. There are two interface demos and one interface for the API documentation, and the readme is refreshingly candid about the difference between them. The first demo is reachable from inside the local network region, is read only, and shows the project's initial state, which makes it the one to look at if you want to see what the author shipped. The second is hosted outside that region, allows any visitor to create, read, update and delete freely, and the readme warns in advance that the data you see may have been altered by previous visitors, that the database is reset at half past four every morning, and that the hosting is free foreign server capacity so it may be slow and may require a proxy to reach. That last note is unusual to find and useful in context, because it tells you the demo is a courtesy rather than a service. The API documentation is the third link and it is the one the quick start points you at, which is the right choice for a backend: the fastest way to see what a project does is its own generated specification.
The whole stack comes up and down through three wrapper scripts:
pnpm docker:up
pnpm docker:down
pnpm docker:logsEach one is a thin wrapper over a compose invocation that layers two environment files, so the same thing can be run directly:
docker compose --env-file .env --env-file .env.production up -d --no-build
docker compose --env-file .env --env-file .env.production logs -fPermissions down to the button, and one initial password for every user
The project summary names its stack and its headline feature in one line: the framework, a relational database, an object-relational mapper, a cache, signed tokens, generated API documentation, and role-based permission management described as fine grained down to the button, with single sign-on also implemented. The button-level part is what distinguishes this from a template that has a roles table and stops. It means the permission model has three layers rather than two: a user has roles, a role has menu and operation permissions, and an operation has a button identifier that the interface uses to decide whether to render a control. That is the model every administrative interface needs and most starter projects only half implement. The second part is single sign-on, and the readme does not describe how it is done, so whether it is a token exchange with an existing session service or something you would need to build is a question for the code. The credentials deserve a paragraph of their own, because they are the first thing an adopting team should change. The readme publishes a table with the super administrator account and its password, and then adds a note that every newly created user has that same initial password. Both facts are public. A starter project that sets a known password on every account it creates is doing the right thing in the sense that nobody is locked out, and the wrong thing in the sense that the first person who registers on your instance knows the password for every account on it. Combined with the demo, where anyone can log in as the super administrator, the security posture of an unmodified deployment is entirely predictable, and the fix is a configuration change rather than a code change.
Three environment files, and types generated from them during install
Configuration is split three ways and the split is documented before anything else, which is a good sign. One file holds settings common to every environment, and two more hold the development and production overrides. That structure exists because the project distinguishes the two in its build and start scripts, setting the environment name explicitly when it runs, and because the two deployment paths read different pairs of files. The mechanism that makes the split worth reading is the type generation. There is a script that reads the environment files and emits TypeScript definitions, and it is wired as a post-install hook, so the types appear when you install rather than when someone remembers to run something. The consequence is that a configuration key that does not exist is a compile error rather than an undefined value at three in the morning, which for a project with a cache, a database and an authentication secret is a real improvement over a framework that reads process variables directly. The same design produces the project's most useful gotcha, and the readme flags it as a tip rather than burying it: the database migration commands run against compiled output, so if your entity classes or database configuration have changed you must build first and only then run migrations. That is not a quirk, it follows from the migration script pointing at the compiled configuration module, and it will cost you an afternoon the first time if you skip it. The three commands you need are:
pnpm migration:run
pnpm migration:generate
pnpm migration:revertThe generate command embeds the package version in the migration filename, with the dots replaced by underscores, so each release gets its own migration folder rather than overwriting the last one. One weakness in the type generation is worth naming, because it is visible in the script itself: the hook is written so that a failure is swallowed and the install continues. A silent failure there leaves you without the generated types and without an error, so if the editor stops flagging unknown configuration keys, that script is the first thing to run by hand.
The entrypoint runs your migrations, and that is a decision with consequences
The container image ends with a single entrypoint line, and it is doing three things at once. It waits for the database to accept connections using a small shell script that is committed to the repository and made executable during the build. It then runs the database migration command. And only if that succeeds does it start the application under a process manager. The waiting is the sensible part, and the fact that it uses a vendored script rather than relying on the compose file's health check conditions is a small portability win, since it works the same way whether the database is a container or a managed service. The migration step is the part with consequences, and there are three. First, a failed migration prevents the service from starting, which is what you want and also means a bad migration takes the application down rather than leaving it serving stale data. Second, the migration runs on every start, so anything expensive runs on every deploy. Third, and this is the one that will bite, two replicas starting at the same time will both run the migration, and the object-relational mapper's migration table is not designed for that race. With one instance it is invisible. The moment you scale the service horizontally you have either serialised the start behind a lock or introduced a race, and the readme does not say which. The fix, if you want it, is to move the migration out of the entrypoint and run it as an explicit step in your deployment, which is a small change to one line in a Dockerfile. One more detail from the same file is worth knowing before you store anything: the image sets the container's time zone to a city in eastern China by symlinking the zone file and writing the zone name, so the clock inside the container is not your host's clock. The last two lines of the file are the whole mechanism:
EXPOSE $APP_PORT
ENTRYPOINT ./wait-for-it.sh $DB_HOST:$DB_PORT -- pnpm migration:run && pm2-runtime ecosystem.config.jsTwo compose files, four services, and a root password matching the application one
There is a development compose file and a production one, and the development file is worth reading line by line because it is both a good template and a list of things to change. Four services. The front end runs as a prebuilt image with a configuration fragment mounted into the server's configuration directory and port 80 published, so the interface is not built from source here, it is pulled as an image and configured. The database runs the latest tag, reads both environment files, and is initialised from a directory of SQL scripts mounted where the database image looks for them on first start, with a comment in the compose file noting that the initialisation will not run if the data directory already exists, which is a detail that bites everyone once. Its character set and collation are set explicitly on the command line. The cache runs an Alpine image with a password required. The application is built from the source tree with a directory argument, waits for the other two, mounts a logs directory outside the container, and is given a host gateway entry so it can reach services running on the machine rather than in the compose network. Now the two things to change. The database's root password is set from the same variable as the application password, and the database port is published to the host, so on a developer machine anyone who can reach that port can be root in your database with a password that is also your application's password. And the host gateway mapping gives the application container the ability to reach services on the host, which is convenient in development and unnecessary in production. Neither is a flaw in a development file, and both are the kind of thing that ends up in production because the production file was copied from the development one.
Five ways to run it, and three package managers in one repository
The script surface is broad enough to be worth mapping, because it tells you what the author expects you to do. There is a watch mode for development that sets the environment name explicitly and passes a specific TypeScript configuration. There is a plain production start that runs the compiled entrypoint under Node. There is a process-manager path with start, restart and stop, driven by a committed ecosystem configuration, and the container uses the runtime form of the same manager. There is a bundle script that compiles and then runs a single-file bundler over the output with source maps and minification and marks the result executable, which gives you a self-contained deployment artefact with no node_modules directory. And there is a script that starts the framework with an alternative entry file to give you a read-eval-print loop with the application's modules already loaded, which for a project with an object-relational mapper and a cache is a genuinely valuable debugging tool rather than a curiosity. Alongside those are the quality scripts: a linter with a fix mode, a test runner, and a documentation generator. Which brings us to the inconsistency worth naming. The project requires a specific package manager, and the readme says so explicitly, yet the scripts themselves invoke the other one, and the container image installs the process manager globally with the third. All three work, because the package manager resolution is a shell question rather than a semantic one, but it means a contributor following the readme will have two tools installed and will be slightly unsure which is in charge. The declared test script is also worth a note: a runner is configured, and no test files appear anywhere in the repository listing, so either the suite is empty or it lives inside the source directory where the listing does not reach.
A four-stage build with a shared dependency cache, and six months of silence
The build is the tidiest part of the repository and worth copying whatever else you take from it. The image is built in stages from a slim Node base. The first stage enables the corepack shim and installs the process manager. Then the source is copied, the wait script is made executable, and the time zone is set. From there the build splits in two: one stage installs production dependencies only, with a frozen lockfile and a build cache mount pointed at the package store, and the other installs everything and compiles. The final stage copies the production dependency tree from the first and the compiled output from the second, so the compiler, the dev dependencies and the intermediate layers never reach the shipped image. Sharing the store cache between the two installs is the detail that makes a cold build bearable, because the second install reuses what the first downloaded instead of fetching it again. The comments throughout the file are tutorial in tone, explaining in prose what the base image directive and the working directory instruction do, which is unusual and slightly awkward for a production Dockerfile but means a newcomer reading it learns something. The one thing to fix is the obsolete version key at the top of the compose files, which current tooling ignores. Then the maintenance position, which needs stating plainly. The last push was on 2026-03-30 and the repository publishes no tagged releases at all, so there is no version to depend on, only a branch. The manifest says version two point oh point oh and marks the package private, which is consistent with a template that is not published to a registry. The readme also credits an earlier starter project as its starting point, which is worth knowing if you want to see the diff between the two. So the honest summary is a competent, well-organised template on a framework that is not deprecated, at a stage where features are being added and releases are not.
Editorial conclusion
nest-admin is a solid starting point if you want a typed, layered API with permissions enforced in the service layer rather than only in the interface, and the choice of an ORM with real migrations plus generated API documentation is the reason to look at it rather than a hand-rolled controller. The interface is a separate repository, so budget for two codebases and two release trains rather than one. Three things to settle before you build on it. The credentials, because the super administrator account and the initial password for every user you create are both printed in the readme, and a starter project that hands every new account the same password needs that changed before it faces a user. The database configuration, because the development compose file publishes the database port to the host and sets the root password to the same value as the application password, which is fine on a laptop and wrong anywhere else. And the migration story, because running migrations from the container entrypoint means a failed migration blocks startup and two replicas starting together will race, so decide whether you accept that or take the migration out of the boot path before you scale out.
Frequently asked questions
Where is the front end for this backend?
In a separate repository, which the readme links from its header. There are two readmes to deploy, they version independently, and this repository's quick start points you at the generated API documentation rather than at an interface. The compose file pulls the interface as a prebuilt image and mounts a server configuration fragment for it.
What are the default credentials?
The readme publishes a table with a super administrator account and the password a123456, and adds that every newly created user starts with that same initial password. Both are public, so changing them is a configuration task to do before anyone else can reach the instance.
Do database migrations run automatically?
Yes. The container entrypoint waits for the database, then runs the migration command, and only starts the application if that succeeds. That means a failed migration blocks startup, migrations run on every deploy, and two replicas starting at the same time will both attempt one.
What does the TypeScript configuration typing do?
A script reads the environment files and emits type definitions, and it is wired as a post-install hook, so an unknown configuration key is a compile error rather than an undefined value at runtime. The hook is written to swallow failures, so if the generated types stop appearing, run the generation script by hand.
What are the runtime requirements?
Node 20 or later, the specified package manager, MySQL 8 or later, and for the containerised path a recent Docker with a compose version of at least 2.17. MySQL and the cache can also be started as individual containers for local development if you do not want to install them.
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/buqiyuan-nest-admin)