How to connect AdminJS to an existing Node.js/Express/TypeScript/MongoDB application
How to connect AdminJS to an existing Node.js/Express/TypeScript/MongoDB application
Today we’re going to build an admin dashboard which can be plugged into your Node.js/Express application using AdminJS — our open-source library that generates a React UI that allows you (or other trusted users) to manage content and save hours of coding.
Connecting AdminJS to your Node.js app
Everything we have managed to create so far is a boilerplate which eventually enables us to test out and comprehend features of the AdminJS.
What is AdminJS?
AdminJS is the world’s leading automatic admin dashboard which can be plugged into your Node.js application. You, as a developer, provide database models (like posts, comments, stores, products or whatever else your application uses), and AdminJS generates a React UI, which allows you (or other trusted users) to manage content. Cool, right? In case you would like to learn more about AdminJS, you should visit our official AdminJS site here, and read some official documentation here.
The main pros of using AdminJS
Admin JS is exactly what you would expect from a decent middleware: installation is fast and easy, it allows you to fetch and analyse data from multiple sources, showcase them in your admin panel and most importantly change how the panel works and looks almost without any limitations.
Connecting an AdminJS dashboard
Let’s start by creating an empty AdminJS instance. As a result, the main dashboard will appear in the browser. For now, it won’t have any resources.
Start by running the command:
npm install adminjs @adminjs/express
Now, to successfully connect your app to the AdminJS, replace all the index.ts code with the below snippet and restart the server.
import express from “express”;
import AdminJS from “adminjs”;
import AdminJSExpress from “@adminjs/express”;const app = express();// Very basic configuration of AdminJS.const adminJs = new AdminJS({
databases: [], // We don’t have any resources connected yet.
rootPath: ‘/admin’, // Path to the AdminJS dashboard.
});// Build and use a router to handle AdminJS routes.const router = AdminJSExpress.buildRouter(adminJs);
app.use(adminJs.options.rootPath, router);// Run the server.app.listen(8080, () =>
console.log(`Example app listening on port 8080!`)
);
When you visit http://localhost:8080/admin again, you will see the AdminJS dashboard for the first time.
Create and pass resources to AdminJS
You’re now ready to add some resources. Working with MongoDB is much easier when you use it together with the Moongose library, and since AdminJS relies on database adapters to handle resources, let’s install both the library and the adapter:
npm install mongoose @adminjs/mongoose
Next, add installed dependencies to the index.ts file and register the adapter so that AdminJS recognizes it:
import mongoose from “mongoose”;
import AdminJSMongoose from “@adminjs/mongoose”;AdminJS.registerAdapter(AdminJSMongoose)…
AdminJS treats everything that you can manage (CRUD: create, read, update, destroy) as a resource. You can add to AdminJS either an entire database or pass each resource separately. We will go for the second option and create 2 resources ‘Users’ and ‘Cities’ since this approach is more flexible. In the root directory of your app create folder Resources and add citites.ts file the with following content:
import { Schema } from “mongoose”;// Define a schema that maps to a MongoDB collection and define the shape of the documents within the selected collection.export const citiesSchema = new Schema({
name: {
// Create a required ‘name’ field.
type: String,
require: true,
},
street: String,
number: Number,
postcode: String,
});
The second file in the Resources folder should be named users.ts and look like this:
import { Schema } from “mongoose”;export const usersSchema = new Schema({
email: {
type: String,
required: true
},
encryptedPassword: {
type: String,
required: true
},
role: {
type: String,
enum: [“admin”, “restricted”],
required: true
},
});
We shall use the users.ts later to create a role-based access control (RBAC) for our AdminJS panel.
For now, let’s go back to the index.ts file and import the created resources:
import { citieSchema } from “./Recources/cities”;
import { usersSchema } from “./Recources/users”;
Then, initialize your database along with models using our resources (you can read more about that in the Moongose documentation):
const Users = mongoose.model(“Users”, usersSchema);
const Cities = mongoose.model(“Cities”, citiesSchema);
Remove the databases key/value pair from the AdminJS instance and pass over resources separately:
const adminJs = new AdminJS({ resources:[
{resource: Users},
{resource: Cities},
], rootPath: “/admin”, // Path to the AdminJS dashboard.
});
Last but not least, we should connect our server to our local MongoDB.
Our MongoDB database runs locally and is available on a default port (27017). Modify a piece of code that runs the server, so that it connects to the database. We can name our database ‘tutorial’ or whatever you like.
Below you will find the whole code of index.ts:
import express from “express”;
import AdminJS from “adminjs”;
import AdminJSExpress from “@adminjs/express”;
import mongoose, {ConnectOptions} from “mongoose”;
import AdminJSMongoose from “@adminjs/mongoose”;
import { citiesSchema } from “./Recources/cities”;
import { usersSchema } from “./Recources/users”;AdminJS.registerAdapter(AdminJSMongoose);const Users = mongoose.model(“Users”, usersSchema);
const Cities = mongoose.model(“Cities”, citiesSchema);const app = express();// Very basic configuration of AdminJS.const adminJs = new AdminJS({
resources: [
{
resource: Users,
},
{
resource: Cities,
},
], rootPath: “/admin”, // Path to the AdminJS dashboard.
});// Build and use a router to handle AdminJS routes.const router = AdminJSExpress.buildRouter(adminJs);
app.use(adminJs.options.rootPath, router);// Run the server.const run = async () => {
await mongoose.connect(“mongodb://localhost:27017/tutorial”, {useNewUrlParser: true,} as ConnectOptions); await app.listen(8080, () => console.log(`Example app listening on port 8080!`));
};run();
Refresh the server one more time and take a look at your browser. Your resources are now visible on the right hand side menu. Play with it by adding, modifying and deleting records.
Tip: At this stage, it is recommended to double-check whether your MongoDB database has been connected to the server correctly. Go back to MongoDB window (command line) and run:
show dbs
A ‘tutorial’ database should be visible in the database list.
Authentication mechanism
Every decent admin panel should have a role-based access control (RBAC) in which passwords are not visible to everybody. Let’s use a bcrypt library to hash the passwords. Install the dependency:
npm install bcrypt
and declaration types:
npm i — save-dev @types/bcrypt
At the same time, it is good when a password is visible when you need to edit it. How to achieve both? Firstly, we need to configure properties in the resource option object. Secondly, we will add an action property to a resource that would handle password hashing.
Modify ‘Users’ resource so that it looks like the below:
…import bcrypt from “bcrypt”;…
{
resource: Users, options: {
properties: {
encryptedPassword: {
isVisible: false,
},
password: {
type: ‘string’,
isVisible: {
// Make password visible in the edit mode.
list: false,
edit: true,
filter: false,
show: false,
},
},
}, actions: {
new: {
// Hash the password.
before: async (request: ActionRequest) => {
if(request?.payload?.password) {
request.payload = {
…request.payload,
encryptedPassword: await bcrypt.hash(request.payload.password, 10),
password: undefined,
}
} return request
},
}
}
}
},…
Save the code, restart the server.
Go to the admin panel, create two different users and assign them different roles. Try to remember or note down their emails and passwords. We will need the users’ credentials to access the admin panel when it finally has a login page.
Adding an AdminJS login page
Users authentication comes out-of-the-box with the previously installed @adminjs/express module. We just need to replace the buildRouter function with the buildAuthenticatedRouter and pass the authentication method to verify an email and a password.
…const router = AdminJSExpress.buildAuthenticatedRouter(
adminJs,
{
cookieName: “adminjs”,
cookiePassword: “complicatedsecurepassword”,
authenticate: async (email, password) => {
const user = await Users.findOne({ email });
if (user) {
const matched = await bcrypt.compare(password, user.encryptedPassword);
if (matched) {
return user;
}
}
return false;
},
},
null, // Add configuration required by the express-session plugin.
{
resave: false,
saveUninitialized: true,
}
);…
However, managing the session will work only with the following installation:
npm install cookie-parser express-session
Now you should see the login page when you restart the server. You should also be able to log in using memoized user credentials.
Restricting access to resources based on user access level
Another feature useful to implement is restricted access to resources based on user access level. We can either restrict access to the entire resource or just the selected records.
First we need to check whether a logged-in user has an admin role with a simple function:
const isAdminRole = ({ currentAdmin }: { currentAdmin: CurrentAdmin }) => {
return currentAdmin && currentAdmin.role === “admin”;
};
The currentAdmin is an object returned in the buildAuthenticatedRouter authenticate function.
In the ‘Cities’ resource, add the options object including actions, which should be available to users with admin role only. Use the isAccessible action parameter:
…{
resource: Cities,
options: {
actions: {
// Restrict resource modification.
edit: { isAccessible: canEditCities },
delete: { isAccessible: canEditCities },
},
},
},…
Users whose roles in AdminJS are different from ‘admin’ are no longer able to modify the ‘Cities’ records.
Bonus: AdminJS modifications (coming soon)
AdminJS comes with a handful of customization options that will further maximize the integration with your existing apps. You can, for instance, add your own branding through visual or translate the entire admin panel to your local language. In fact, you can modify the admin dashboard to fit your needs and create custom pages, as required.
If any of these features sound appealing, we’re working on an ebook that will include this section, describing each modification with concrete examples. You can subscribe to our newsletter here and we’ll send the PDF as soon as it’s out. Or, if you prefer using Twitter, you can follow us here and we’ll keep you updated that way.
Connecting the admin dashboard to your existing application
As you can see, linking AdminJS with your existing Node.js/Express application isn’t hard. We hope you found the instruction useful and that your admin dashboard is now fully up and running, and indeed helps you save hours of coding.
If you still have doubts about the process or other questions about AdminJS, just join our Slack, where you can get help both from the core AdminJS team as well as our community members!