Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions examples/clean-architecture/controllers/note.controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
var Note = require("../entities/note.entity");

/**
* Please, note that here I'm not using ES6 classes just for compatibility purposes
* In a real-world application, you should use ES6 classes
*/
function NoteController(noteUseCase) {
this.noteUseCase = noteUseCase;
}

NoteController.prototype.getAll = function (req, res) {
var notes = this.noteUseCase.getAll();
res.json(notes);
};

NoteController.prototype.getById = function (req, res) {
var noteId = parseInt(req.params.id);
var note = this.noteUseCase.getById(noteId);
res.json(note);
};

NoteController.prototype.create = function (req, res) {
var note = new Note(req.body.title, req.body.content);
var createdNote = this.noteUseCase.create(note);
res.status(201).json(createdNote);
};

NoteController.prototype.update = function (req, res) {
var noteId = parseInt(req.params.id);
this.noteUseCase.updateById(noteId, req.body.title, req.body.content);
res.json("Note updated");
};

NoteController.prototype.delete = function (req, res) {
var noteId = parseInt(req.params.id);
this.noteUseCase.delete(noteId);
res.json("Note deleted");
};

module.exports = NoteController;
43 changes: 43 additions & 0 deletions examples/clean-architecture/entities/note.entity.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
var isEmptyString = function (str) {
return str === undefined || str === null || str.trim() === "";
};
var throwIfEmpty = function (str, field) {
if (isEmptyString(str)){
var error = new Error("field '" + field + "' should not be empty");
error.status = 400;
throw error
}
};

function Note(title, content, createdAt, updatedAt, id) {
throwIfEmpty(title, "title");

this.id = id;
this.title = title;
this.content = content;
this.createdAt = createdAt;
this.updatedAt = updatedAt;
}

Note.prototype.setId = function (id) {
this.id = id;
};

Note.prototype.setTitle = function (title) {
throwIfEmpty(title, "title");
this.title = title;
};

Note.prototype.setContent = function (content) {
this.content = content;
};

Note.prototype.setCreatedAt = function (createdAt) {
this.createdAt = createdAt;
};

Note.prototype.setUpdatedAt = function (updatedAt) {
this.updatedAt = updatedAt;
};

module.exports = Note;
35 changes: 35 additions & 0 deletions examples/clean-architecture/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
'use strict'

/**
* Module dependencies.
*/

/**
* This example is a Clean Architecture implementation of a CRUD application.
* You have only the Note entity and 4 rest api endpoints:
* - GET /note
* - GET /note/:id
* - POST /note
* - PATCH /note/:id
* - DELETE /note/:id
*/

var express = require('../..');
var logger = require('morgan');
var loadRoutes = require('./routes');

var app = module.exports = express();

// log
if (!module.parent) app.use(logger('dev'));

// parse request bodies (req.body)
app.use(express.json());

loadRoutes(app)

/* istanbul ignore next */
if (!module.parent) {
app.listen(3000);
console.log('Express started on port 3000');
}
60 changes: 60 additions & 0 deletions examples/clean-architecture/repositories/note.repository.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
function NotesRepository() {
//Really simple in-memory database
this.notes = [];
this.autoIncrementID = 0;
}

NotesRepository.prototype.getAll = function() {
return this.notes;
};

NotesRepository.prototype.getById = function(id) {
var result = this.notes.find(function(note) {
return note.id === id;
});
if (typeof result === "undefined") {
var err = new Error("Note not found");
err.status = 404;
throw err
}
return result;
};

NotesRepository.prototype.create = function(note) {
note.setId(this.autoIncrementID);
note.setCreatedAt(new Date());
note.setUpdatedAt(new Date());
this.autoIncrementID++;

this.notes.push(note);

return note;
};

NotesRepository.prototype.deleteById = function(id) {
var index = this.notes.findIndex(function(note) {
return note.id === id;
});
if (index === -1) {
var err = new Error("Note not found");
err.status = 404;
throw err
}
this.notes.splice(index, 1);
};

NotesRepository.prototype.updateById = function(id, title, content) {
var index = this.notes.findIndex(function(note) {
return note.id === id;
});
if (index === -1) {
var err = new Error("Note not found");
err.status = 404;
throw err
}
this.notes[index].setTitle(title);
this.notes[index].setContent(content);
this.notes[index].setUpdatedAt(new Date());
};

module.exports = NotesRepository;
4 changes: 4 additions & 0 deletions examples/clean-architecture/routes/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
module.exports = function loadRoutes(app) {
//Add here your routes
app.use("/notes", require("./notes.route"));
};
24 changes: 24 additions & 0 deletions examples/clean-architecture/routes/notes.route.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
var router = require('../../..').Router();

//Database connection (you can change it to use a different database)
var NotesRepository = require('../repositories/note.repository');

//Controller class to handle express requests
var NotesController = require('../controllers/note.controller');

//Business logic class
var NotesService = require('../use-cases/notes');

var db = new NotesRepository()
var businessLogic = new NotesService(db)
var controller = new NotesController(businessLogic)


router.get('/', controller.getAll.bind(controller));
router.get('/:id', controller.getById.bind(controller));
router.post('/', controller.create.bind(controller));
router.put('/:id', controller.update.bind(controller));
router.delete('/:id', controller.delete.bind(controller));


module.exports = router;
31 changes: 31 additions & 0 deletions examples/clean-architecture/use-cases/notes/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/**
* Since this is just a simple CRUD, I'm only calling the underlying repository methods.
* In a real world application, in the same use-case method, you will insert the entire business logic.
*/

function NotesService(notesRepository) {
this.notesRepository = notesRepository;
}

NotesService.prototype.getAll = function() {
return this.notesRepository.getAll();
};

NotesService.prototype.getById = function(id) {
return this.notesRepository.getById(id);
};

NotesService.prototype.create = function(note) {
//Add here other business logic...
return this.notesRepository.create(note);
};

NotesService.prototype.updateById = function(id, title, content) {
return this.notesRepository.updateById(id, title, content);
};

NotesService.prototype.delete = function(id) {
return this.notesRepository.deleteById(id);
};

module.exports = NotesService;
136 changes: 136 additions & 0 deletions test/acceptance/clean-architecture.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
var app = require("../../examples/clean-architecture");
var request = require("supertest");

describe("clean-architecture-crud", function () {
describe("GET /", function () {
it("should return empty array", function (done) {
request(app).get("/notes").expect(200, [], done);
});

it("list after creation", function (done) {
request(app)
.post("/notes")
.set("Content-Type", "application/json")
.send('{"title": "Text"}')
.expect(201)
.then(function (res) {
const obj = {
title: "Text",
id: 0,
};
request(app)
.get("/notes")
.expect(function (res) {
obj.createdAt = res.body[0].createdAt;
obj.updatedAt = res.body[0].updatedAt;
})
.expect(200, [obj], done);
});
});
});

describe("GET /:id", function () {
it("get after creation", function (done) {
request(app)
.post("/notes")
.set("Content-Type", "application/json")
.send('{"title": "Text"}')
.expect(201, function (err, res) {
if (err) return done(err);
request(app)
.get("/notes/" + res.body.id)
.expect(200, function (err, res) {
if (err) return done(err);
if (res.body.title === "Text") {
done();
}
});
});
});

it("return error if cannot find by id", function (done) {
request(app).get("/notes/100").expect(404, done);
});
});

describe("POST /", function () {
it("validation error on title", function (done) {
request(app)
.post("/notes")
.set("Content-Type", "application/json")
.send('{"title": ""}')
.expect(400, done);
});

it("creation ok", function (done) {
request(app)
.post("/notes")
.set("Content-Type", "application/json")
.send('{"title": "Text"}')
.expect(201, done);
});
});

describe("POST /", function () {
it("validation error on title", function (done) {
request(app)
.post("/notes")
.set("Content-Type", "application/json")
.send('{"title": ""}')
.expect(400, done);
});
});

describe("DELETE /", function () {
it("delete after creation", function (done) {
request(app)
.post("/notes")
.set("Content-Type", "application/json")
.send('{"title": "Text"}')
.expect(201, function (err, res) {
if (err) return done(err);
request(app)
.delete("/notes/" + res.body.id)
.expect(200, done);
});
});

it("delete unexisting node", function (done) {
request(app).delete("/notes/100").expect(404, done);
});
});

describe("put /", function () {
it("update after creation", function (done) {
request(app)
.post("/notes")
.set("Content-Type", "application/json")
.send('{"title": "Text"}')
.expect(201, function (err, res) {
if (err) return done(err);
request(app)
.put("/notes/" + res.body.id)
.send({ title: "Test2" })
.expect(200, '"Note updated"', done);
});
});

it("error in update after creation", function (done) {
request(app)
.post("/notes")
.set("Content-Type", "application/json")
.send('{"title": "Text"}')
.expect(201, function (err, res) {
if (err) return done(err);
request(app)
.put("/notes/" + res.body.id)
.send({ title: "" })
.expect(400, done);
});
});

it("try to update post not found", function (done) {
request(app).put("/notes/100").send({ title: "Test1" }).expect(404, done);
});
});
});