A lightweight REST API for user registration, login, JWT authentication, protected profile management, admin user management and password reset workflows.
User Management Service is a backend service built with Node.js, Express, MongoDB and JWT.
The project demonstrates a realistic user management API with registration, login, password hashing, JWT-based authentication, protected profile routes, admin user management, account status handling and password reset workflows.
It follows a modular backend structure using controllers, routes, middleware, services, utilities and Mongoose models.
The API is deployed on Render and can be accessed here:
https://user-management-service-1jgc.onrender.com
Health check:
https://user-management-service-1jgc.onrender.com/health
Swagger API documentation:
https://user-management-service-1jgc.onrender.com/api-docs
Note: The service is hosted on Render's free tier. The first request after a period of inactivity may take a little longer because the service can spin down when idle.
Swagger UI is available after starting the application:
http://localhost:3000/api-docs
| Feature | Description |
|---|---|
| User Registration | Create a new user account with strong password validation |
| Password Hashing | Passwords are hashed with bcrypt |
| Login | Authenticate user credentials and return a JWT token |
| JWT Authentication | Protect API routes with Bearer Token authentication |
| User Profile | Get and update the current authenticated user profile |
| Password Change | Change the current user's password after password verification |
| Forgot Password | Request a password reset link by email |
| Reset Password | Reset password with a time-limited reset token |
| Password Policy | Enforce strong passwords for registration, change and reset |
| Admin User Management | List users and manage account status |
| Account Status Control | Activate or disable user accounts |
| MongoDB | Store user data persistently |
| Swagger/OpenAPI | Interactive API documentation |
| Automated Tests | API testing with Jest and Supertest |
| Docker Support | Containerized application and database |
| CI/CD Pipeline | Automated test execution with GitHub Actions |
- Node.js
- Express.js
- MongoDB
- Mongoose
- JWT
- bcrypt
- Nodemailer
- Swagger / OpenAPI
- Jest
- Supertest
- Docker
- Docker Compose
- dotenv
- GitHub Actions
| Method | Endpoint | Description | Auth |
|---|---|---|---|
| GET | /health |
Check API service status | No |
| POST | /auth/register |
Register a new user | No |
| POST | /auth/login |
Login and receive JWT token | No |
| POST | /auth/forgot-password |
Request a password reset email | No |
| POST | /auth/reset-password |
Reset password with a valid reset token | No |
| GET | /users/me |
Get current user profile | Bearer Token |
| PUT | /users/me |
Update current user profile | Bearer Token |
| PATCH | /users/me/password |
Change current user password | Bearer Token |
| GET | /users |
List all users | Admin only |
| PATCH | /users/:id/status |
Activate or disable a user account | Admin only |
Protected endpoints require a valid Bearer token.
For every protected request, the API checks the current user record in the database. This means that user roles and account status are evaluated from the latest database state, not only from the JWT payload.
Disabled users cannot access protected endpoints, even if they still have an older valid token. This applies to regular users and admins.
If a user account no longer exists, protected requests return 404 Benutzer nicht gefunden.
This behavior prevents outdated tokens from keeping access after an account has been disabled, deleted, or downgraded.
The API supports a backend-only password reset flow.
A user can request a password reset link with POST /auth/forgot-password. If the email belongs to an active account, the API creates a reset token and sends a reset link by email.
For security reasons, the raw reset token is never stored in the database. Only a SHA-256 hash of the token is stored together with an expiration timestamp.
Password reset tokens expire after 15 minutes by default. The expiry time can be configured with PASSWORD_RESET_TOKEN_EXPIRY_MINUTES.
The API returns a generic response for unknown email addresses. This helps prevent user enumeration.
Disabled users cannot request or use password reset tokens.
After a successful password reset, the stored reset token hash and expiration timestamp are cleared so the same token cannot be reused.
The same strong password policy is applied to:
- User registration
- Password change
- Password reset
Passwords must:
- Be between 12 and 128 characters long
- Include at least one lowercase letter
- Include at least one uppercase letter
- Include at least one number
- Include at least one special character
Whitespace does not count as a special character.
user-management-service/
├── .github/
│ └── workflows/
│ └── test.yml
├── controllers/
│ ├── authController.js
│ └── userController.js
├── docs/
│ └── swagger-ui.png
├── middleware/
│ └── authMiddleware.js
├── models/
│ └── user.js
├── routes/
│ ├── authRoutes.js
│ └── userRoutes.js
├── services/
│ └── emailService.js
├── swagger/
│ └── userSwagger.js
├── tests/
│ ├── admin.api.test.js
│ ├── auth.api.test.js
│ └── health.test.js
├── utils/
│ └── passwordPolicy.js
├── .dockerignore
├── .env.example
├── .gitignore
├── app.js
├── docker-compose.yml
├── Dockerfile
├── index.js
├── package.json
├── package-lock.json
└── README.md
Create a local .env file based on .env.example.
PORT=3000
MONGODB_URI=mongodb://127.0.0.1:27017/user-management
JWT_SECRET=your_jwt_secret_here
APP_BASE_URL=http://localhost:3000
SMTP_HOST=sandbox.smtp.mailtrap.io
SMTP_PORT=2525
SMTP_USER=your_mailtrap_username
SMTP_PASS=your_mailtrap_password
SMTP_FROM="User Management Service <no-reply@example.com>"
PASSWORD_RESET_TOKEN_EXPIRY_MINUTES=15For local email testing, Mailtrap Email Sandbox can be used as an SMTP provider. Real SMTP credentials must be configured in the local .env file or in the Render environment settings. Secrets are not committed to the repository.
git clone https://github.com/tabari86/user-management-service.git
cd user-management-service
npm installMake sure MongoDB is running locally.
npm startServer:
http://localhost:3000
Swagger UI:
http://localhost:3000/api-docs
Run the complete application stack:
docker compose up --buildFor the first startup, Docker will automatically:
- Build the Node.js application image
- Pull the MongoDB image
- Create the required containers
- Create a persistent MongoDB volume
Services:
- Node.js application
- MongoDB database
The API will be available at:
http://localhost:3000
Swagger UI:
http://localhost:3000/api-docs
Stop the containers:
docker compose downThis project uses GitHub Actions to automatically run the test suite on every push and pull request to the main branch.
Workflow steps:
- Checkout repository
- Setup Node.js
- Install dependencies
- Start MongoDB service
- Run Jest and Supertest tests
This helps ensure that new changes do not break existing functionality.
Implemented:
- User registration
- Login
- JWT authentication
- Protected profile route
- Profile update route
- MongoDB connection
- Swagger documentation
- Automated tests with Jest and Supertest
- Dockerfile
- Docker Compose setup
- GitHub Actions CI workflow
- Admin user management
- Account activation and disabling
- Password change endpoint
- Forgot password flow
- Reset password flow
- Strong password policy
- Mailtrap SMTP integration for password reset emails
Planned improvements:
- Metrics endpoint
- Refresh token flow
curl -X POST http://localhost:3000/auth/register \
-H "Content-Type: application/json" \
-d "{\"name\":\"Test User\",\"email\":\"testuser@example.com\",\"password\":\"TestPassword123!\"}"curl -X POST http://localhost:3000/auth/login \
-H "Content-Type: application/json" \
-d "{\"email\":\"testuser@example.com\",\"password\":\"TestPassword123!\"}"curl http://localhost:3000/users/me \
-H "Authorization: Bearer YOUR_JWT_TOKEN"- A user registers with email, password and name.
- The password is hashed with bcrypt.
- The user logs in with email and password.
- The API returns a JWT token.
- Protected routes require:
Authorization: Bearer YOUR_JWT_TOKEN- Passwords are never stored as plain text.
- Passwords are hashed with bcrypt.
- Strong password validation is applied to registration, password change and password reset.
- Password reset tokens are not stored as plain text.
- Password reset tokens are stored as SHA-256 hashes with an expiration timestamp.
- Reset tokens are cleared after successful password reset.
- Protected routes require a valid JWT token.
- Protected routes check the current user status from the database.
- Secret values are stored in
.env. - Only
.env.exampleis committed to the repository.
Moj Tabari
Website: https://mtintelligence.ai
GitHub: https://github.com/tabari86
