Lewati ke konten utama

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/uploads sebelum menguji upload.
  • Untuk produksi, gunakan storage provider (S3, GCS) daripada disk lokal.