JWT Auth + File Uploads
Contoh ini memperluas modul Users dengan JWT authentication dan endpoint upload menggunakan Multer. Pastikan Anda sudah memiliki tabel users dari panduan Build Your First API.
1. Install dependencies
npm install jsonwebtoken bcrypt multer
2. Konfigurasi JWT secret
Tambahkan secret di 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 dengan validasi
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 + wiring container
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. Coba dengan 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 (ganti TOKEN)
curl -X POST http://localhost:3387/uploads \
-H 'Authorization: Bearer TOKEN' \
-F 'file=@/path/to/avatar.png'
Catatan
- Buat folder
storage/uploadssebelum menguji upload. - Untuk produksi, gunakan storage provider (S3, GCS) daripada disk lokal.