Role-Based Access Control in Express.js + GraphQL: Who Gets to Do What?

Author

Kritim Yantra

Aug 12, 2025

Role-Based Access Control in Express.js + GraphQL: Who Gets to Do What?

We’ve done:

  1. Intro to Express.js + GraphQL
  2. Connecting to a Database
  3. Adding Authentication

Now it’s time for Role-Based Access Control (RBAC) in Express.js + GraphQL — so you can give different permissions to different users. 

The "Oops" That Happens Without Roles…

Imagine you’re running a website where people can write blog posts.
One day, a regular user logs in and… somehow deletes every post on the site.

Not fun. 😬

This happens when all authenticated users are treated the same.
But in real-world apps, not everyone should have the same power — admins should be able to manage everything, while regular users can only do certain things.

That’s where Role-Based Access Control (RBAC) comes in.


Why RBAC Matters

  • Protects your data — Only trusted users can perform sensitive actions.
  • Keeps users happy — Prevents accidental (or intentional) damage.
  • Scales well — Easier to manage permissions for thousands of users.

Step 1: Add a Role to Your User Model

We’ll give each user a role field.
"USER" is the default, "ADMIN" has all the powers.

const userSchema = new mongoose.Schema({
  name: String,
  email: { type: String, unique: true },
  password: String,
  role: { type: String, default: 'USER' } // USER or ADMIN
});

Step 2: Update the GraphQL Schema

We’ll add a query that only admins can use.

const schema = buildSchema(`
  type User {
    id: ID
    name: String
    email: String
    role: String
  }

  type Query {
    me: User
    allUsers: [User] # Admin only
  }

  type Mutation {
    register(name: String!, email: String!, password: String!, role: String): String
    login(email: String!, password: String!): String
  }
`);

Step 3: Create an authorize Helper

Instead of sprinkling if (user.role !== 'ADMIN') everywhere, we’ll make a reusable function.

function authorize(user, roles = []) {
  if (!user) throw new Error('Not authenticated');
  if (roles.length && !roles.includes(user.role)) {
    throw new Error('Not authorized');
  }
}

Step 4: Secure Resolvers with RBAC

const root = {
  me: async (args, context) => {
    authorize(context.user);
    return await User.findById(context.user.id);
  },
  allUsers: async (args, context) => {
    authorize(context.user, ['ADMIN']);
    return await User.find();
  },
  register: async ({ name, email, password, role }) => {
    const user = new User({ name, email, password, role });
    await user.save();
    return jwt.sign({ id: user.id, role: user.role }, SECRET, { expiresIn: '1d' });
  },
  login: async ({ email, password }) => {
    const user = await User.findOne({ email });
    if (!user) throw new Error('User not found');
    const valid = await bcrypt.compare(password, user.password);
    if (!valid) throw new Error('Invalid password');
    return jwt.sign({ id: user.id, role: user.role }, SECRET, { expiresIn: '1d' });
  }
};

Step 5: Testing Role-Based Access

  1. Register an Admin
mutation {
  register(name: "Admin", email: "admin@example.com", password: "adminpass", role: "ADMIN")
}
  1. Register a Regular User
mutation {
  register(name: "Bob", email: "bob@example.com", password: "bobpass")
}
  1. Try allUsers Query with Bob's Token → ❌ Access Denied
  2. Try allUsers Query with Admin's Token → ✅ Success

Pro Tip 💡

Think of RBAC like a VIP party:

  • USER: Regular guest — can enjoy the drinks.
  • ADMIN: Party host — can kick people out, change the music, and open the VIP lounge.

Common Beginner Questions (FAQ)

Q1: Can a user have multiple roles?
A: Yes! Just store roles as an array and check accordingly.

Q2: What if I want more fine-grained control?
A: You can implement permission-based access instead of role-based (e.g., "CAN_DELETE_POST").

Q3: How do I promote a user to admin?
A: Add an admin-only mutation that updates the role field in the database.


Wrapping Up

You’ve now:

  • Added roles to your GraphQL users
  • Secured certain actions for admins only
  • Built a cleaner, reusable authorize helper

Your API is now safer and ready for real-world multi-user scenarios.

Comments

No comments yet. Be the first to comment!

Please log in to post a comment:

Sign in with Google

Related Posts