Node.js Clean Architecture ile REST API(Express) – 02

Estimated reading time: 34 minute(s)

Merhaba, bu yazımda sizlere önceki yazıda yaptığımız clean architecture üzerine bir api geliştireceğiz. Apimiz gerçek bir mysql veritabanı kullanarak temel CRUD işlemlerini gerçekleştirecek.

Eğer okumadıysanız ilk yazıyı okumanızı tavsiye ederim.

03. Config

Projemizde kullanacağımız değerler için config klasörü altında constants.js ve environment.js dosyalarımızı oluşturuyoruz.

constants.js

JWT için kullanacağımız projemizin secret key’ini burada tanımlıyoruz. Burada hash veya uuid kullanmalısınız.

'use strict';

module.exports = {

  JWT: {
    SECRET_KEY: 'custom-secret-key'
  }

};

environment.js

Mysql veritabanınızın bilgilerini aşağıdaki uygun yerlere eklemelisiniz.

'use strict';

module.exports = (() => {

    const environment = {
        database: {
            username: "",
            password: "",
            database: "",
            host: "",
            dialect: "mysql"
        }
    };

    if (process.env.NODE_ENV === 'dev') {

    }

    return environment;
})();

04. Controllers Katmanının Oluşturulması

Controller’lar ile Usecase’lerimizi çağıracağız ve Controllar’larımız daha sonra oluşturacağımız Api katmanında çalışacak.

Senaryomuz gereği iki adet controllar tanımlayacağız.

AuthController’ımız sadece basit bit JWT authentication ile token üretecek. PersonController’ımız ise CRUD işlemlerimizden mesul olacak.

AuthController.js

JWT secret key’imizin tanımlı olduğu constants.js dosyasını ve jwt kütüphanemiz olan ‘jsonwebtoken’ ı Controllar’ımıza dahil ediyoruz.

Gerekli paketler;
npm install jsonwebtoken

'use strict';
const constant = require('../../src/config/constants');
const ResponseDto = require('../core/dtos/ResponseDto');
const AuthDto = require('../core/dtos/AuthDto');
const LoginPerson = require('../core/usecases/LoginPerson');
const jwt = require('jsonwebtoken');

module.exports = class AuthController {

    constructor(personRepository) {
        this.personRepository = personRepository;
    }

    async login(req) {
        var response = await LoginPerson(req, this.personRepository);
        if (response != null) {
            const token = jwt.sign(
                {
                    id: response.id,
                    email: response.email,
                },
                constant.JWT.SECRET_KEY,
                {
                    expiresIn: '2h',
                },
            );
            return new ResponseDto("ok", new AuthDto(token), 200);
        }
        else {
            return new ResponseDto("bad request", null, 400);
        }
    }
}

PersonController.js

'use strict';

const ResponseDto = require('../core/dtos/ResponseDto');
const GetPerson = require('../core/usecases/GetPerson');
const GetAllPersons = require('../core/usecases/GetAllPersons');
const CreatePerson = require('../core/usecases/CreatePerson');
const UpdatePerson = require('../core/usecases/UpdatePerson');
const DeletePerson = require('../core/usecases/DeletePerson');

module.exports = class PersonController {

    constructor(personRepository) {
        this.personRepository = personRepository;
    }

    async getPerson(personId) {
        var response = await GetPerson(personId, this.personRepository);
        if (response == null) return new ResponseDto("bad request", null, 400);
        return new ResponseDto("person", response, 200);
    }
    async listPerson() {
        var response = await GetAllPersons(this.personRepository);
        if (response == null) return new ResponseDto("bad request", null, 400);
        return new ResponseDto("person list", response, 200);
    }
    async createPerson(req) {
        var response = await CreatePerson(req, this.personRepository);
        if (response == null) return new ResponseDto("bad request", null, 400);
        return new ResponseDto("created", response, 201);
    }
    async updatePerson(personId, req) {
        var response = await UpdatePerson(personId, req, this.personRepository);
        if (response == null) return new ResponseDto("bad request", null, 400);
        return new ResponseDto("updated", response, 200);
    }
    async deletePerson(personId) {
        var response = await DeletePerson(personId, this.personRepository);
        if (response > 0) return new ResponseDto("bad request", null, 400);
        return new ResponseDto("deleted", response, 200);
    }
}

05. Infrustructure Katmanının Oluşturulması

Bu katmanımız frameworkleri ve entegrasyonları içerir.

Sequelize ile Mysql Entegrasyonu

Veritabanımızda işlemlerimizi Sequelize ORM (Object Relational Mapping) ‘ini kullanarak yapacağız. Gerekli klasörleri oluşturalım.

infrustructure/orm/sequelize/models

Şimdi Sequelize’ın kullanacağı veritabanımızın modellemelerini yapalım.

infrustructure/orm/sequelize/models/Person.js

module.exports = (sequelize, DataTypes) => {
    const tableName = "Person";
    sequelize.define(tableName, {
        id: {
            primaryKey: true,
            autoIncrement: true,
            type: DataTypes.INTEGER,
            allowNull: false
        },
        firstName: {
            type: DataTypes.STRING,
            allowNull: false
        },
        lastName: {
            type: DataTypes.STRING,
            allowNull: false
        },
        email: {
            type: DataTypes.STRING,
            allowNull: false
        },
        password: {
            type: DataTypes.STRING,
            allowNull: false
        }
    }, {
        tableName: tableName,
        timestamps: false
    });
};

Sequelize yapılandırmamızı tamamlayalım.

infrustructure/orm/sequelize/sequelize.js

Gerekli paketler;
npm install sequelize
npm install mysql2

'use strict';

const { Sequelize } = require('sequelize');
const environment = require('../../../config/environment');

const sequelize = new Sequelize(
    environment.database.database,
    environment.database.username,
    environment.database.password,
    {
        host: environment.database.host,
        dialect: environment.database.dialect
    });

// Modellerimizi burada import ediyoruz.
sequelize.import('./models/Person');

module.exports = sequelize;

Repository’lerin Oluşturulması

Core katmanımızdan hatırlarsak constracts olarak PersonRepository.js tanımlamıştık. Şimdi Mysql kullanan person repository’mizi yazalım.

infrustructure/repositories/PersonRepository.js

Contract’ta belirlediğimiz tüm metodlarımızı burada implement ediyoruz. Eğer implement etmezsek, PersonRepository Contract’ımızda bulunan ‘not implemented’ hatası oluşacaktır.

'use strict';

const sequelize = require('../orm/sequelize/sequelize');
const PersonRepository = require('../../core/contracts/PersonRepository');

module.exports = class extends PersonRepository {

    constructor() {
        super();
        this.db = sequelize;
        this.model = this.db.model('Person');
    }

    async get(personId) {
        return await this.model.findOne({
            where: {
                id: personId
            }
        });
    }

    async list() {
        return await this.model.findAll();
    }

    async create(request) {
        return await this.model.create(request);
    }

    async update(personId, request) {
        return await this.model.update(request, {
            where: { id: personId }
        });
    }

    async delete(personId) {
        return await this.model.destroy({
            where: {
                id: personId
            }
        });
    }

    async login(request) {
        return await this.model.findOne({
            where: {
                email: request.email,
                password: request.password
            }
        });
    }
};

Sıra geldi bağımlılık üretmeden repository’mizi kullanmaya. Bunun için bu örnekte Service Locator pattern’i kullanacağız. Başka bir yol olarak dependency injection için awilix veya inversify kütüphaneleri kullanılabilir.

infrustructure/servicelocator.js

'use strict';

const PersonRepository = require('../infrustructure/repositories/PersonRepository');

function services() {
    const services = {};

    services.personRepository = new PersonRepository();

    return services;
}

module.exports = services();

05. Api Katmanının Oluşturulması

Artık sona geldik. Api katmanımızı tamamlayıp uygulamamızı çalıştıralım.

api/server.js  – Api’yi ayağa kaldırdığımız dosyamız.

'use strict';

const createServer = async () => {

    var express = require('express');
    const bodyParser = require('body-parser');
    const routes = require('./routes');
    const serviceLocator = require('../infrustructure/servicelocator');

    var app = express();
    var PORT = 3000;

    app.use(bodyParser.urlencoded({ extended: true }));
    app.use(bodyParser.json());

    app.set('serviceLocator', serviceLocator);

    app.use('/api', routes(app.get('serviceLocator')));

    app.listen(PORT, function (err) {
        if (err) console.log(err);
        console.log("Server listening: ", "http://localhost:" + PORT + "/api");
    });
}

module.exports = createServer;

Api’miz için Route’larımızı tanımlayalım.

api/middleware/checkauth.js

const jwt = require('jsonwebtoken');

const constant = require('../../config/constants');

module.exports = (req, res, next) => {
    const authHeader = req.headers.authorization;
    if (authHeader) {
        const token = authHeader.split(' ')[1];

        jwt.verify(token, constant.JWT.SECRET_KEY, (err, user) => {
            if (err) {
                return res.sendStatus(403);
            }
            req.user = user;
            next();
        });

    } else {
        res.sendStatus(401);
    }
};


api/routes/index.js – Bu dosyamız router görevi görmekte, tüm route’larımızı burada ekliyoruz.

'use strict';

const express = require('express');
const checkAuth = require('../middleware/checkauth');
const auth = require('./auth');
const persons = require('./persons');

const apiRouter = (serviceLocator) => {
    const routes = express.Router();

    routes.use('/auth', auth(serviceLocator));
    // Kullanıcı doğrulama işlemi burada middleware ile yapılıyor.
    routes.use('/', checkAuth);

    routes.use('/persons', persons(serviceLocator));

    return routes;
};


module.exports = apiRouter;

api/routes/auth.js

'use strict';

const express = require('express');

const AuthController = require('../../controllers/AuthController');

// api/auth
const authRouter = ({ personRepository }) => {
    const router = express.Router();
    
    const authController = new AuthController(personRepository);

    router.route('/')
        .post(async function (req, res) {
            var result = await authController.login(req.body);
            res.status(result.statusCode).send(result)
        });
    return router;
};


module.exports = authRouter;

api/routes/person.js

const express = require('express');

const PersonController = require('../../controllers/PersonController');

// api/persons
const personsRouter = ({ personRepository }) => {
    const router = express.Router();
    const personController = new PersonController(personRepository);

    router.route('/')
        .get(async function (req, res) {
            var result = await personController.listPerson();
            res.status(result.statusCode).send(result)
        })
        .post(async function (req, res) {
            var result = await personController.createPerson(req.body);
            res.status(result.statusCode).send(result)
        });

    router.route('/:personId')
        .get(async function (req, res) {
            var result = await personController.getPerson(req.params.personId);
            res.status(result.statusCode).send(result)
        })
        .delete(async function (req, res) {
            var result = await personController.deletePerson(req.params.personId);
            res.status(result.statusCode).send(result)
        })
        .put(async function (req, res) {
            var result = await personController.updatePerson(req.params.personId, req.body)
            res.status(result.statusCode).send(result)
        });
    return router;
};

module.exports = personsRouter;

Proje dizinimizin son hali aşağıdadır.

Uygulamamız bitti, şimdi test edelim.

Öncelikle veritabanında Person tablosuna daha önceden eklediğim bir kullanıcı ile tokenı alalım. Dilerseniz CreatePerson’ı CheckAuth işlemi öncesine taşıyıp uygulama üzerinden de ilk kullanıcıyı oluşturabilirsiniz.

Şimdi aldığımız token’ı kullanarak yeni bir kullanıcı ekleyelim.

Son eklediğimiz kullanıcıyı getirelim.

Projenin tamamına aşağıdan ulaşabilirsiniz;
https://github.com/YildirimMehmet/nodejs-rest-api-with-clean-architecture

Api’ye validasyon eklemek isterseniz aşağıdaki yazımı okumanızı öneririm.

Bu yazımızında sonuna geldik. Umarım sizin için faydalı olmuştur.

Okuduğunuz için teşekkür ederim.

Bir cevap yazın

E-posta hesabınız yayımlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir