/how-to-build-replit

How to Build a User permission management with Replit

Learn how to build a user permission management system with Replit. Follow this guide to manage roles, access levels, and secure your web app easily.

Matt Graham, CEO of Rapid Developers

Book a call with an Expert

Starting a new venture? Need to upgrade your web app? RapidDev builds application with your growth in mind.

How to Build a User permission management with Replit

A practical way to build a user permission management system on Replit is to create a Node.js + Express backend with authentication (using hashed passwords and JWT tokens) and a simple middleware that checks user roles (like “admin”, “editor”, “viewer”). You’ll store user data and their roles in a database (for Replit demos, SQLite works great and is included by default). Then, you add API routes that only certain roles can access. This approach works well on Replit and is easy to deploy, test, and extend — it’s also safe to use with Replit Secrets for environment variables.

 

Set up your structure

 

Create the following files and folders in your Replit project root:

  • index.js – Your main server file (Express app setup)
  • db.js – Handles SQLite connection
  • auth.js – Contains login/signup logic and JWT generation
  • middleware/permission.js – Middleware to check if the user has the required role

Also, go to the “Secrets” tab in Replit and create a new secret called JWT\_SECRET with any secure random string (this will keep JWT signing safe).

 

Install dependencies

 

npm init -y
npm install express sqlite3 bcryptjs jsonwebtoken

 

Create db.js

 

// db.js
const sqlite3 = require('sqlite3').verbose();

const db = new sqlite3.Database(':memory:'); // for demo, use in-memory; or './users.db' for a file

// Create users table with name, password and role
db.serialize(() => {
  db.run('CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, username TEXT, password TEXT, role TEXT)');
});

module.exports = db;

 

Create auth.js

 

// auth.js
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');
const db = require('./db');

const JWT_SECRET = process.env.JWT_SECRET;

// Register new user
function register(username, password, role, callback) {
  bcrypt.hash(password, 10, (err, hash) => {
    if (err) return callback(err);
    db.run(
      'INSERT INTO users (username, password, role) VALUES (?, ?, ?)',
      [username, hash, role],
      function (dbErr) {
        if (dbErr) return callback(dbErr);
        callback(null, { id: this.lastID, username, role });
      }
    );
  });
}

// Login existing user
function login(username, password, callback) {
  db.get('SELECT * FROM users WHERE username = ?', [username], (err, user) => {
    if (err || !user) return callback(err || new Error('User not found'));
    bcrypt.compare(password, user.password, (compErr, isMatch) => {
      if (!isMatch) return callback(new Error('Invalid password'));
      const token = jwt.sign(
        { id: user.id, username: user.username, role: user.role },
        JWT_SECRET,
        { expiresIn: '1h' }
      );
      callback(null, token);
    });
  });
}

module.exports = { register, login };

 

Create middleware/permission.js

 

// middleware/permission.js
const jwt = require('jsonwebtoken');
const JWT_SECRET = process.env.JWT_SECRET;

function authorizeRole(allowedRoles) {
  return (req, res, next) => {
    const authHeader = req.headers.authorization;
    if (!authHeader) return res.status(403).json({ message: 'No token provided' });
    const token = authHeader.split(' ')[1];
    jwt.verify(token, JWT_SECRET, (err, decoded) => {
      if (err) return res.status(403).json({ message: 'Invalid token' });
      if (!allowedRoles.includes(decoded.role)) {
        return res.status(403).json({ message: 'Permission denied' });
      }
      req.user = decoded;
      next();
    });
  };
}

module.exports = authorizeRole;

 

Create the main server index.js

 

// index.js
const express = require('express');
const app = express();
const db = require('./db');
const { register, login } = require('./auth');
const authorizeRole = require('./middleware/permission');

app.use(express.json());

// Endpoint to register new user
app.post('/register', (req, res) => {
  const { username, password, role } = req.body;
  register(username, password, role, (err, user) => {
    if (err) return res.status(400).json({ error: err.message });
    res.json({ message: 'User registered', user });
  });
});

// Endpoint to login
app.post('/login', (req, res) => {
  const { username, password } = req.body;
  login(username, password, (err, token) => {
    if (err) return res.status(400).json({ error: err.message });
    res.json({ token });
  });
});

// Protected route for admin only
app.get('/admin', authorizeRole(['admin']), (req, res) => {
  res.json({ message: `Welcome, ${req.user.username}! You have admin access.` });
});

// Protected route for editors and admins
app.get('/editor', authorizeRole(['admin', 'editor']), (req, res) => {
  res.json({ message: `Hello ${req.user.username}, you can edit content.` });
});

app.listen(3000, () => {
  console.log('Server running on port 3000');
});

 

How to run and test it on Replit

 

  • Press the green “Run” button — Replit will start your server on a visible URL.
  • Use the “Shell” or Replit’s built-in “HTTP client” tab to test endpoints.
  • POST to /register with JSON like:
    {"username":"alice","password":"1234","role":"admin"}
  • POST to /login to get a JWT token.
  • Use that token as Authorization header (Bearer <token>) when calling /admin or /editor.

 

Replit-specific tips

 

  • Secrets: use them to store JWT\_SECRET or DB connection strings instead of hardcoding.
  • Persistence: the in-memory DB resets when the repl restarts. To persist, change ':memory:' to './users.db'.
  • Logs: use Replit’s console to debug output; add console.log() statements if you need to trace requests.
  • Collaboration: other team members can join the repl in “multiplayer” mode and see code changes live.

 

That’s a complete runnable user permission management setup that fits Replit’s environment perfectly — simple to maintain, test, and safe to deploy for demos or small projects.

Want to explore opportunities to work with us?

Connect with our team to unlock the full potential of no-code solutions with a no-commitment consultation!

Contact Us

How to Build a Role-Based Access Control API with Express and Replit Database



import express from "express";
import Database from "@replit/database";
import jwt from "jsonwebtoken";
import bodyParser from "body-parser";

const app = express();
const db = new Database();
app.use(bodyParser.json());

const SECRET = process.env.JWT\_SECRET;

// Seed roles and permissions if they don't exist
const seedPermissions = async () => {
  const existing = await db.get("roles");
  if (!existing) {
    await db.set("roles", {
      admin: ["manage_users", "view_data", "edit\_data"],
      editor: ["view_data", "edit_data"],
      viewer: ["view\_data"]
    });
  }
};
seedPermissions();

app.post("/login", async (req, res) => {
  const { username, role } = req.body;
  const roles = (await db.get("roles")) || {};
  if (!roles[role]) return res.status(400).send("Invalid role");
  const token = jwt.sign({ username, role }, SECRET, { expiresIn: "2h" });
  res.json({ token });
});

const checkPermission = (permission) => async (req, res, next) => {
  const header = req.headers.authorization;
  if (!header) return res.status(401).send("Token missing");
  try {
    const token = header.split(" ")[1];
    const decoded = jwt.verify(token, SECRET);
    const roles = (await db.get("roles")) || {};
    const userPermissions = roles[decoded.role] || [];
    if (!userPermissions.includes(permission)) return res.status(403).send("Forbidden");
    req.user = decoded;
    next();
  } catch {
    res.status(401).send("Invalid token");
  }
};

app.get("/users", checkPermission("manage\_users"), async (req, res) => {
  const users = (await db.list()).filter(k => k.startsWith("user:"));
  res.json(users);
});

app.listen(3000, () => console.log("🚀 Permission API running on port 3000"));

How to Sync User Roles with an External API in Replit



import express from "express";
import Database from "@replit/database";
import fetch from "node-fetch";

const app = express();
const db = new Database();

// Example use case: Sync role changes from an external admin API (e.g., your org’s central RBAC service)
app.post("/sync-roles", async (req, res) => {
  try {
    const response = await fetch(process.env.EXTERNAL_ROLE_API\_URL, {
      headers: { Authorization: `Bearer ${process.env.EXTERNAL_API_TOKEN}` },
    });
    if (!response.ok) return res.status(502).send("Failed to fetch roles from external service");
    
    const externalRoles = await response.json();
    const localRoles = (await db.get("roles")) || {};

    // Merge new roles while preserving local permissions for comparison or rollback
    const updatedRoles = { ...localRoles, ...externalRoles };
    await db.set("roles", updatedRoles);

    res.json({ message: "Roles synced successfully", updatedRoles });
  } catch (err) {
    console.error("Role sync failed:", err);
    res.status(500).send("Unable to sync roles");
  }
});

app.listen(4000, () => console.log("🔄 Role sync service running on port 4000"));

How to Audit User Roles and Permissions in Replit with Express



import express from "express";
import Database from "@replit/database";
import jwt from "jsonwebtoken";

const app = express();
const db = new Database();
const SECRET = process.env.JWT\_SECRET;

app.use(express.json());

// Middleware to ensure only admins can trigger audits
const requireAdmin = async (req, res, next) => {
  const header = req.headers.authorization;
  if (!header) return res.status(401).send("Missing token");
  try {
    const token = header.split(" ")[1];
    const decoded = jwt.verify(token, SECRET);
    if (decoded.role !== "admin") return res.status(403).send("Forbidden");
    req.user = decoded;
    next();
  } catch {
    res.status(401).send("Invalid token");
  }
};

// Specific use case: detect any users assigned to non-existent roles
app.get("/audit/roles", requireAdmin, async (req, res) => {
  try {
    const keys = await db.list("user:");
    const users = await Promise.all(keys.map(async k => await db.get(k)));
    const roles = (await db.get("roles")) || {};
    const invalidAssignments = users
      .filter(u => !roles[u.role])
      .map(u => ({ username: u.username, invalidRole: u.role }));
    res.json({ invalidAssignments });
  } catch (err) {
    console.error("Audit failed:", err);
    res.status(500).send("Audit error");
  }
});

app.listen(5000, () => console.log("🧩 Role audit API running on port 5000"));

Want to explore opportunities to work with us?

Connect with our team to unlock the full potential of no-code solutions with a no-commitment consultation!

Contact Us
Matt Graham, CEO of Rapid Developers

Book a call with an Expert

Starting a new venture? Need to upgrade your web app? RapidDev builds application with your growth in mind.

Best Practices for Building a User permission management with Replit

When building a user permission management system in Replit, keep the logic simple and structured. Use role-based access control (RBAC) or permission-based checks stored in a database (like Replit’s built-in Database or an external one such as MongoDB Atlas). Store credentials and JWT secrets using the Replit Secrets tool, not in your code. Your main backend file (e.g., index.js for Node or main.py for Python) should handle routes, authentication, and role validations. Keep permission logic modular, like in a separate auth.js middleware or permissions.js helper.

 

Folder Structure and Setup

 

Organize your files clearly inside your Replit project so permissions and authentication are readable and manageable.

  • index.js — main server entry.
  • auth.js — middleware for verifying login and roles.
  • permissions.js — helper defining allowed actions by role.
  • .env (Replit Secrets) — define your JWT secret or API keys using Replit’s “Secrets” panel, not plaintext.

 

Step 1: Basic Express Server (index.js)

 

// index.js
import express from "express";
import jwt from "jsonwebtoken";
import { checkRole } from "./auth.js";

const app = express();
app.use(express.json());

// Example users stored in memory for demo
const users = [
  { id: 1, username: "adminUser", role: "admin" },
  { id: 2, username: "editorUser", role: "editor" },
  { id: 3, username: "viewerUser", role: "viewer" }
];

// Generate token route
app.post("/login", (req, res) => {
  const { username } = req.body;
  const user = users.find(u => u.username === username);
  if (!user) return res.status(403).send("Invalid username");
  
  // Secret stored in Replit Secrets panel as JWT_SECRET
  const token = jwt.sign({ id: user.id, role: user.role }, process.env.JWT_SECRET);
  res.json({ token });
});

// A route restricted by role
app.get("/admin-data", checkRole("admin"), (req, res) => {
  res.send("Secure admin data here");
});

// Start the server
app.listen(3000, () => console.log("Server running on port 3000"));

 

Step 2: Create Middleware (auth.js)

 

This file verifies tokens and checks if the user’s role fits the route requirement. Create auth.js in the root of your Replit project.

// auth.js
import jwt from "jsonwebtoken";

export function checkRole(requiredRole) {
  return (req, res, next) => {
    const authHeader = req.headers.authorization;
    if (!authHeader) return res.status(401).send("No token provided");

    const token = authHeader.split(" ")[1];
    try {
      const decoded = jwt.verify(token, process.env.JWT_SECRET);
      if (decoded.role !== requiredRole) {
        return res.status(403).send("Insufficient permissions");
      }
      req.user = decoded;
      next();
    } catch (err) {
      res.status(401).send("Invalid token");
    }
  };
}

 

Step 3: Define Permission Matrix (permissions.js)

 

You can extend roles and permissions easily from here. For complex projects, store these in a database table.

// permissions.js
export const rolesPermissions = {
  admin: ["read", "write", "delete"],
  editor: ["read", "write"],
  viewer: ["read"]
};

// Helper to verify a user's action
export function can(role, action) {
  return rolesPermissions[role]?.includes(action);
}

 

Step 4: Storing Secrets Properly

 

Never hardcode secrets directly in code. In Replit:

  • Go to the padlock icon (Secrets panel) in the left sidebar.
  • Set Key = JWT\_SECRET and Value = any long random string.
  • Your code will access it via process.env.JWT\_SECRET.

 

Step 5: Using Databases in Replit (if needed)

 

If you need persistent user data, connect Replit DB or MongoDB:

// Example using Replit DB for simple user storage
import Database from "@replit/database";
const db = new Database();

await db.set("users", users);
const storedUsers = await db.get("users");

 

Practical Replit Tips

 

  • Use the "Always On" feature (if available) to keep your permission system active for API routes.
  • Keep private data (like JWT secrets) in Secrets. Anyone with Editor access to the Repl can see environment variables, so never store passwords there directly.
  • Use Git integration to version control permission logic changes.
  • Test endpoints directly in the Replit shell using curl or in-built browser tab.

 

This setup is minimal but production-valid in Replit’s sandbox. It keeps authentication and permission checks modular, secrets secure, and logic easy to extend — matching how a senior developer would structure Node-based projects inside Replit’s environment.

Client trust and success are our top priorities

When it comes to serving you, we sweat the little things. That’s why our work makes a big impact.

Rapid Dev was an exceptional project management organization and the best development collaborators I've had the pleasure of working with. They do complex work on extremely fast timelines and effectively manage the testing and pre-launch process to deliver the best possible product. I'm extremely impressed with their execution ability.

CPO, Praction - Arkady Sokolov

May 2, 2023

Working with Matt was comparable to having another co-founder on the team, but without the commitment or cost. He has a strategic mindset and willing to change the scope of the project in real time based on the needs of the client. A true strategic thought partner!

Co-Founder, Arc - Donald Muir

Dec 27, 2022

Rapid Dev are 10/10, excellent communicators - the best I've ever encountered in the tech dev space. They always go the extra mile, they genuinely care, they respond quickly, they're flexible, adaptable and their enthusiasm is amazing.

Co-CEO, Grantify - Mat Westergreen-Thorne

Oct 15, 2022

Rapid Dev is an excellent developer for no-code and low-code solutions.
We’ve had great success since launching the platform in November 2023. In a few months, we’ve gained over 1,000 new active users. We’ve also secured several dozen bookings on the platform and seen about 70% new user month-over-month growth since the launch.

Co-Founder, Church Real Estate Marketplace - Emmanuel Brown

May 1, 2024 

Matt’s dedication to executing our vision and his commitment to the project deadline were impressive. 
This was such a specific project, and Matt really delivered. We worked with a really fast turnaround, and he always delivered. The site was a perfect prop for us!

Production Manager, Media Production Company - Samantha Fekete

Sep 23, 2022