After some time poorly designing my web applications' backends (mixing database calls with the controller, etc.), I have decided to try the "Clean Architecture" approach.
In this example I have a REST api users which allows you to get a list of all users in MongoDB and to put a user inside the database.
Please, any suggestion on how I can make it better organized would be awesome.
app.js
const express = require("express");
const bodyParser = require("body-parser");
const config = require("./config.js");
const Routes = require("./src/routes.js");
const Database = require("./src/database.js");
const app = express();
app.use(bodyParser.json());
const PORT = config.PORT;
app.use("/api/", Routes());
new Database(config.MONGODB_URI) // connects to the database using MONGODB cluster URL
.then(() => {
app.listen(config.PORT, () => {
console.log(`Server running on port ${config.PORT}`);
});
})
.catch((err) => console.error(err));
/src/routes.js
const express = require("express");
const usersRouter = require("./user/routes.js");
const Routes = (dependencies) =>
{
const router = express.Router();
router.use("/users", usersRouter(dependencies));
return router;
};
module.exports = Routes;
/src/database.js
const mongoose = require("mongoose");
module.exports = class Database{
constructor(connection){
this.connection = connection;
return mongoose.connect(this.connection, {
useNewUrlParser: true,
useUnifiedTopology: true
});
}
};
src/user/routes.js
const express = require("express");
const UserController = require("./controller.js");
const UserModal = require("./data_access/modal.js");
const UserRepository = require("./repository.js");
const userRoutes = () => {
const modal = UserModal; // pretty much the User modal/document
const repository = new UserRepository(modal); // talks to the db
const router = express.Router();
const controller = UserController(repository); // handles request, sends repository to services
router.route('/')
.get(controller.getUsers)
.post(controller.addUser);
return router;
};
module.exports = userRoutes;
src/user/repository.js
module.exports = class UserRepository{
constructor(model){
this.model = model;
}
create(user){
return new Promise((resolve, reject) =>{
this.model(user).save();
resolve(user);
});
}
getByEmail(email){
return new Promise((resolve, reject) =>{
this.model.find({email: email})
.then((user) => resolve(user[0]));
});
}
getByUsername(username){
return new Promise((resolve, reject) =>{
this.model.find({username: username})
.then((user) => resolve(user[0]));
});
}
getAll(){
return new Promise((resolve, reject) => {
const students = this.model.find({});
resolve(students);
});
}
};
src/user/controller.js
const GetUsers = require("./services/get_users.js");
const AddUser = require("./services/add_user.js");
module.exports = (repository) => {
const getUsers = (req, res) => {
GetUsers(repository)
.execute()
.then((result) => res.sendStatus(200).json(result))
.catch((err) => console.error(err));
};
const addUser = (req, res) => {
const { username, email } = req.body;
AddUser(repository)
.execute(username, email)
.then((result) => res.send(200))
.catch((err) => {
res.send(403);
console.error(err);
});
};
return {
getUsers,
addUser
};
};
/src/user/services/add_user.js
module.exports = (repository) =>
{
async function execute(username, email){
return Promise.all([repository.getByUsername(username), repository.getByEmail(email)])
.then((user) => {
if(user[0]){
return Promise.reject("username already taken!");
}
else if(user[1]){
return Promise.reject("email already taken!");
}
else{
repository.create({username: username, email: email});
return Promise.resolve("all good!");
}
});
}
return {execute};
};
src/user/services/get_user.js
module.exports = (repository) =>
{
async function execute(){
const users = repository.getAll();
return new Promise((resolve, reject) => resolve(users));
}
return {execute};
};
src/user/data_access/schema.js (entities)
const mongoose = require("mongoose");
module.exports = new mongoose.Schema(
{
username: {
type: String
},
email: {
type: String
},
password_hash: {
type: String
}
});
src/user/data_access/model.js (entities)
const mongoose = require("mongoose");
const userSchema = require("./schema.js");
const UserModal = mongoose.model("User", userSchema);
module.exports = UserModal;