JWT Auth + File Uploads
This example extends the Users module with JWT authentication and a Multer upload endpoint. It assumes you already have the users table from the Build Your First API guide.
1. Install dependencies
npm install jsonwebtoken bcrypt multer
2. Configure JWT secret
Add a secret to your environment:
JWT_SECRET=super-secret-value
3. Auth service (JWT + bcrypt)
app/Modules/Users/Auth.service.js
const HttpError = require('../../Errors/HttpError');
const jwt = require('jsonwebtoken');
const bcrypt = require('bcrypt');
class AuthService {
constructor({ models } = {}) {
this.usersModel = models.user;
}
async register(payload) {
const passwordHash = await bcrypt.hash(payload.password, 10);
const id = await this.usersModel.save({
name: payload.name,
email: payload.email,
password_hash: passwordHash,
});
return this.usersModel.getSingle(id);
}
async login({ email, password }) {
const user = await this.usersModel.findByEmail(email);
if (!user) {
throw new HttpError(401, 'Invalid credentials');
}
const valid = await bcrypt.compare(password, user.password_hash);
if (!valid) {
throw new HttpError(401, 'Invalid credentials');
}
const token = jwt.sign(
{ id: user.id, email: user.email },
process.env.JWT_SECRET,
{ expiresIn: '2h' }
);
return { token, user: { id: user.id, name: user.name, email: user.email } };
}
}
module.exports = AuthService;
4. Auth controller with validation
app/Modules/Users/Auth.controller.js
const HttpError = require('../../Errors/HttpError');
class AuthController {
constructor({ services, validationFactory } = {}) {
this.authService = services.auth;
this.validationFactory = validationFactory;
}
get registerRules() {
return {
name: { required: true },
email: { required: true, is_email: true },
password: { required: true, minLength: 8 },
};
}
get loginRules() {
return {
email: { required: true, is_email: true },
password: { required: true },
};
}
async register(req, res) {
const validator = this.validationFactory(this.registerRules);
const errors = await validator.validate(req.body);
if (errors.length > 0) {
throw new HttpError(422, 'Validation failed', errors);
}
const user = await this.authService.register(req.body);
return res.status(201).json(user);
}
async login(req, res) {
const validator = this.validationFactory(this.loginRules);
const errors = await validator.validate(req.body);
if (errors.length > 0) {
throw new HttpError(422, 'Validation failed', errors);
}
const result = await this.authService.login(req.body);
return res.json(result);
}
}
module.exports = AuthController;
5. JWT middleware
app/Middleware/auth.middleware.js
const jwt = require('jsonwebtoken');
const HttpError = require('../Errors/HttpError');
module.exports = function authMiddleware(req, _res, next) {
const header = req.headers.authorization || '';
const token = header.replace('Bearer ', '').trim();
if (!token) {
return next(new HttpError(401, 'Missing auth token'));
}
try {
req.user = jwt.verify(token, process.env.JWT_SECRET);
return next();
} catch (error) {
return next(new HttpError(401, 'Invalid auth token'));
}
};
6. Upload controller (Multer)
app/Modules/Users/Uploads.controller.js
class UploadsController {
async create(req, res) {
return res.status(201).json({
file: {
originalName: req.file.originalname,
mimeType: req.file.mimetype,
size: req.file.size,
path: req.file.path,
},
});
}
}
module.exports = UploadsController;
7. Routes + container wiring
app/Modules/Users/users.routes.js
const multer = require('multer');
const authMiddleware = require('../../Middleware/auth.middleware');
const upload = multer({ dest: 'storage/uploads' });
function registerUsersRoutes({ router, container } = {}) {
if (!router || !container) {
return router;
}
const authController = container.resolve('modules.users.controllers.auth');
const uploadsController = container.resolve('modules.users.controllers.uploads');
router.post('/auth/register', authController.register.bind(authController));
router.post('/auth/login', authController.login.bind(authController));
router.post(
'/uploads',
authMiddleware,
upload.single('file'),
uploadsController.create.bind(uploadsController)
);
return router;
}
module.exports = registerUsersRoutes;
app/Modules/Users/users.container.js
const { asClass, asValue } = require('awilix');
const AuthService = require('./Auth.service');
const AuthController = require('./Auth.controller');
const UploadsController = require('./Uploads.controller');
const registerUsersRoutes = require('./users.routes');
module.exports = (scope) => {
scope.register({
services: {
auth: asClass(AuthService).singleton(),
},
controllers: {
auth: asClass(AuthController).singleton(),
uploads: asClass(UploadsController).singleton(),
},
routes: asValue(registerUsersRoutes),
});
};
8. Try it with curl
# Register
curl -X POST http://localhost:3387/auth/register \
-H 'Content-Type: application/json' \
-d '{"name":"Rani","email":"rani@flowra.dev","password":"secret123"}'
# Login
curl -X POST http://localhost:3387/auth/login \
-H 'Content-Type: application/json' \
-d '{"email":"rani@flowra.dev","password":"secret123"}'
# Upload (replace TOKEN)
curl -X POST http://localhost:3387/uploads \
-H 'Authorization: Bearer TOKEN' \
-F 'file=@/path/to/avatar.png'
Notes
- Create the folder
storage/uploadsbefore testing uploads. - For production, use a storage provider (S3, GCS) instead of local disk.