DEV Community

Cover image for Designing a better architecture for a Node.js API

Designing a better architecture for a Node.js API

Thiago Pacheco on November 02, 2019

Some time ago I made a post about creating a Fullstack project with Node.js, React.js, and MongoDB. This is a very cool starter project that could ...
Collapse
 
alexn93 profile image
Aleksandar Nikolov

I tried adding another model(Comment) that is related to Post, but when I try to add .populate('comments') to getAll method in Service class I get
MissingSchemaError: Schema hasn't been registered for model "Comment".
This is how I've added it to Post model
comments: [
{
type: Schema.Types.ObjectId,
ref: 'Comment'
}
],

Collapse
 
pacheco profile image
Thiago Pacheco

Hi Aleksandar,

The property ref must receive the name of the schema you added in mongoose.model, not the name of the class you created.
For example, if your Comment schema is exporting the following: mongoose.model("comments"), then your ref should be: 'ref': 'comments'.

Let me know if that makes sense and if it solves the problem :)

Collapse
 
jonsilver profile image
Jon Silver

Great article about how to start off a project and how to modularise your code. But it's not about architecting an API. This isn't architecture. Please don't call it architecture, you'll confuse people. If you want to talk about the architecture of an API, at the very least that would require a huge amount more background, a much more complex real-world based scenario, a lot of discussion, and a series of articles with diagrams.

Collapse
 
hagen00 profile image
Hagen

Nonsense...so what is your definition of architecture then? Giving background and a real world example over and above what's written here would all of a sudden qualify this post of being worthy of being labelled a architecture discussion? Utter nonsense.

Good article on how to architect your node apps. Thanks Thiago.

Collapse
 
jonsilver profile image
Jon Silver

Architecture isn't how to organise stuff. You don't tell your kid to "go upstairs and architect your room". Architecture is an altogether higher level discipline. As I said, it's a good article. But it's not about architecture.

Thread Thread
 
hagen00 profile image
Hagen

There is no professional body (especially with regards to JS dev) that will define exactly what "architecture" is and isn't. For me, architecture is a very loose term that can mean many things, code structure being one of them. Personally, when I think "application architecture" then the data model is fundamental and most important - but the code structure is also a part of it (as is choice of technology etc).

I think we might actually agree (that architecture is a loose and general term), I just felt it unnecessarily pedantic to moan about the title of this post when it's perfectly "fine".

"Structuring your Node.js code in a better way" just doesn't have the same ring to it!

Thread Thread
 
jonsilver profile image
Jon Silver

😁

Thread Thread
 
willallendev profile image
willallendev • Edited

developer.android.com/jetpack/guide

Android team seems to agree that this is app architecture.

If this is like "sending your kids to architect their bedrooms", doing it at a higher level would be just telling someone to choose who is going to organize a room.

This is just an architecture, planning and adacting an architecture if no just "where to put the code" is also about breaking dependencies, how each the different parts of the code interact with each other in a correct manner so each of them can be easily be manteined and tested and also be able to change technologies, and apis without having to touch a large number of files in the project. Doing app architecture at a higher level is more or less the same, just at a higher level... (highly abstracted, It could be harder or it could be easier it depends on many variables and kinds of project)

Thread Thread
 
jonsilver profile image
Jon Silver

No. Architecture is not organisation. It is about deciding how a process is systemised. It's the difference between a bottlenecked system and one that's almost infinitely scalable. It's how flexible and adaptable the system is to fit the underlying process it's running.

Everything you describe is not architecture. It's being tidy and organised. It's just low-grade devops. Sure the Android team thinks that's architecture, because let's face it, Android doesn't run at the Enterprise level, it runs at single device level. But I'd rather they didn't pollute the namespace by usurping irreplaceable words which already have established meaning.

A construction site should be kept tidy and organised for health & safety reasons. But that's not architecture. Folder structures for Node.js apps are just a way of keeping your construction site tidy & organised.

Misusing terms creates confusion. There's already enough of that. Let's not create even more.

Collapse
 
lechip profile image
Oscar Barrios

I disagree. That is one interpretation of an application architecture. However is not the only one. I wrote my Master Thesis on exploring the concept of software achitechture and among the conclusions is that, by necessity, the scope of "the architecture" is not always tangible or strict. It can (but more often is not) be defined for one single functionality, for the database, for the logging, or for every and any part of the application.
The tangible documentation of the architecture is usually referenced on older literature, but modern software solutions have extrapolated multiple layers of complexity and tend to be higher level, thus defining the architecture on a separate set of documents, ideas or mandates is less and less practical (where do we stop if we have an N number of references, do we specify those as well? do we use partial documentation of "obvious" dependencies? what if we need to take over one of those dependencies? where do we stop the abstractions? when is it more pragmatic to make an actual implementation that is self documenting?).
Often architecture is interpreted as "how to organize the project" and for many authors that means the way the project is literally organized on a file system is an implicit declaration of it's architecture. Under that construct, it is completely valid to say that this article defines the application's architecture in it's own realization.
The purpose of my thesis was to explicitly demystify and make the concept of architecture less esoteric, since is supposed to be a tool for a more concrete plan of realization of an application. Another conclusion is that things like a simple diagram could define an architecture and that any author can express it as detailed and using as many tools as the intention (which is express the organization for a purpose) require or was practically desired. Under those circumstances, declaring that this cannot be called architecture is up actually up to the author and whoever receives this information, if it helps clarify how the application works, then definitely serves as the architecture. The recipient of the information cancan request a more concrete definition of any of the parts (but I can hardly find a more concrete way than the code explained). Architecture in software serves the purpose of communication. The tools could be the discussion, articles or diagrams, but that on their own do not exclusively define the architecture.

Collapse
 
ujwaldhakal profile image
ujwal dhakal

Architecture is a way of solving the problem so for solving the problem one has to organize the structure so that one can find and fix the right solutions to the right issue. So there is a architecture involved ... it could be a traditional MVC all i can see is model & controller

Collapse
 
iangrainger profile image
IanGrainger • Edited

Removed.

Collapse
 
pacheco profile image
Thiago Pacheco

I don't have much experience writing anything not even in my native language. I am just trying to share something that could be helpful for somebody.
If you could tell me what is wrong with the article I appreciate, that would help me and others that want to share something.

Collapse
 
scripturecoder profile image
Samson Akinfenwa • Edited

Thanks so much for this article Bro. and please keep up the good work.

Thread Thread
 
pacheco profile image
Thiago Pacheco

Thank you very much Samson, I appreciate it! :)

Collapse
 
tejeshjadhav profile image
Tejesh Jadhav

You've done a big favor writting this post. I was digging for 2 days inorder to find this way of implementation.
Thanks Buddy, Cheers!

Thread Thread
 
pacheco profile image
Thiago Pacheco

Thank you very much Tajesh! This is really motivating.
Let me know if you have any doubts about it.

Collapse
 
gronkdaslayer profile image
gronkdaslayer

Oh the irony! "The rest if that"???
Don't be such a douche bud, especially when you can't do any better. That post is about programming, it's not a spelling bee or a grammar contest. Worse case, you can mention and correct the mistakes, so that he will learn.

You don't have to say that it's a waste of your precious time because nobody gives a damn, and it makes you look like a jerk.

Collapse
 
sonicoder profile image
Gábor Soós • Edited

It's the very reason why I use Grammarly 🚀. Grammarly is free if you only want to correct grammatical errors.

Collapse
 
pacheco profile image
Thiago Pacheco

Thank you for the suggestion, Grammarly is really amazing and I should have used that in the beginning.

Collapse
 
iangrainger profile image
IanGrainger

So you're saying Grammarly doesn't work?

Thread Thread
 
sonicoder profile image
Gábor Soós

Is there a grammatical error in my sentence? 😂 The plugin says it is fine 😎

Thread Thread
 
iangrainger profile image
IanGrainger

Yes. Two.

Thread Thread
 
sonicoder profile image
Gábor Soós

I'm listening.

Thread Thread
 
garretmh profile image
Garret

I'm pretty sure it's fine. To me, it's slightly strange when you say "it is" instead of "it's" and when you say "if you want only to" rather than "if you only want to". They're not actually wrong though.

Thread Thread
 
sonicoder profile image
Gábor Soós

Thanks for the feedback 👍

Collapse
 
varjmes profile image
james

Two Grammatical errors does not necessitate this comment, to be honest. Nor does it equal a lack of respect. I'm sure not everything you've ever written has been the height of the English language?

Collapse
 
iangrainger profile image
IanGrainger

I didn't mean to be unkind. Just please get these things checked! :)

Collapse
 
cellofan profile image
David Yu

“I don’t want to read the rest if that, thanks.” Oh the irony.

Collapse
 
songthamtung profile image
songthamtung

Typically for architecture related posts, there's a high level diagram of what's happening. I don't see any here. Consider adding one 🙂.

Collapse
 
pacheco profile image
Thiago Pacheco

Thank you for your suggestion, I am going to add that.

Collapse
 
javamhan profile image
Norbin Astrero

Hi can you help me test the API using insomia.
What would be my URL for POST?

Thanks in advance!

Thread Thread
 
pacheco profile image
Thiago Pacheco

Hi Norbin,

You can test the app sending a post request to localhost:5000/api/post

Thread Thread
 
javamhan profile image
Norbin Astrero

Yes done i've already did that, I also tried localhost:5000/api/getAll.

Im getting error below.

Cannot POST /api/post
Cannot GET /api/getAll

Collapse
 
mridul1024 profile image
Mridul Sharma • Edited

Thanks for this post Thiago! Its really helpful. By the way, I also have 2 questions to ask you -
Question 1 -> Do we need to write the business logic only inside the service module?
Question 2 -> Should we separate the database queries into a new module (a repository) or is it better to structure the application without a repository like you did?

Collapse
 
pacheco profile image
Thiago Pacheco

Hi Mridul, thanks for the feedback!
For the question one, yes the main idea was to have the business logic in the service layer.
For the second question I totally agree that it would be great to separate the database queries into a repository. I didn’t separate it like that in this tutorial for simplicity purposes.
I have created recently another structure that might accommodate these changes and some other features. For this new one I am using typescript and I also have the tests setup.
I am thinking about creating a series of articles about it, do you think it would be a helpful?
Thanks again!

Collapse
 
mridul1024 profile image
Mridul Sharma

Yes, It would definitely help me and other beginners like me. I was also looking for some good articles on unit testing and integration testing. Will it be possible for you to incorporate that in your articles?

Collapse
 
dadobaag profile image
DaDoBaag

Yes please make an article and/or repository about this architecture with TypeScript. I'm struggling to create a strictly typed backend with Express + Mongoose.

Thread Thread
 
pacheco profile image
Thiago Pacheco

I'll try to work on a tutorial about it this weekend. I can send you a repo earlier if you'd like to play around with the idea.

Thread Thread
 
dadobaag profile image
DaDoBaag

Awesome, I'd love to get a link to the repo!

Collapse
 
junaidlodhi7 profile image
Muhammad Junaid Lodhi • Edited

@thiago Pacheco: I like the implementation of the architecture,
I am having problem runnning the project can you help me out.

in Base Controller, I get this error when I run the project.
this.get = this.get.bind(this);

can not read property bind of undefined.

NOTE:I have not changed anything and trying to run the code

Can you please me in running code so I can continue working on it?

Collapse
 
pacheco profile image
Thiago Pacheco

Hi Muhammad,

Thank you for sharing that.
Actually, this is an extra line that was not supposed to be in this file.
I was trying to reduce the size of the project to make it simpler, but I forgot to remove this call.
You can just remove the following line from the Controller constructor:
this.get = this.get.bind(this);

I already fixed it in the post, sorry for the error.

Let me know if that worked for you.

Collapse
 
junaidlodhi7 profile image
Muhammad Junaid Lodhi

It worked for me, Thanks a lot .

I also removed

server.get(/api/post/:params, PostController.get);
from routes.js

Thanks a lot.

Thread Thread
 
pacheco profile image
Thiago Pacheco

Perfect Muhammad, thank you very much!

Collapse
 
bencodegeek profile image
Ben Coleman • Edited

Nice post
However your get() method in the Controller class refers to a method on the service instance also called get() which doesn't exist. Service class has getAll, insert, update and delete methods but not get

Collapse
 
pacheco profile image
Thiago Pacheco

Thank you for seeing that Ben, I was trying to reduce the functions because the post does was very long but I've forgotten to remove this one too.
I've updated the post now.

Collapse
 
reynaldohub profile image
reynaldo-hub • Edited

In the official mongoose documentation they refer to using the save() instead of create().

I'm getting an error while trying to use the save() function, I've tried to fix this (maybe I'm being a fancy), but I couldn't.

// this.model.save();

It would be great if we could solve this.

Collapse
 
pacheco profile image
Thiago Pacheco

Hey Reynaldo,

Mongoose also offers the create method that does basically the same thing, and it also offers some support to save multiple documents, check it out here: mongoosejs.com/docs/api.html#model...

Could you share the error you are getting in this part and the source code, maybe we can help you.:)

Collapse
 
reynaldohub profile image
reynaldo-hub

I didn't know that calling create was triggering the same effects as calling the save() function, it was guiding me from this post (twm.me/correct-way-to-use-mongoose/).

Maybe this is no longer necessary, but I was getting an output of (this.model.save () is not a function).

Very good job by the way.

Collapse
 
aashir1 profile image
Aashir Khan

Great article but The first image of "Create the server" heading Line no 5 is "app.use(bodyParser.json())" while it should be "server.use(bodyParser.json())"
because there is no variable with name "app".
Thanks

Collapse
 
pacheco profile image
Thiago Pacheco

That is totally right Aashir, thank you very much for reporting that. I just fixed the post :)

Collapse
 
qleoz12 profile image
qleoz12

hey thanks in advance for this tutorial, but I download your repo from github and make this post with postman and dont works, stay Sending request.. and no work, dont return nothing, some advice could you give me ?

Collapse
 
pacheco profile image
Thiago Pacheco

Hi, are you sending a JSON body in your POST request? Do any errors show up in your terminal?
If you could, show me how you are making the request, please.

Collapse
 
qleoz12 profile image
qleoz12 • Edited

Dont worry master, my bad because Im begginer and I have no installed mongoDB for that no show nothing,any error or any response, after I installed mongo all works like a charm;otherwise can you help me with other thing? please?, im trying to do this...
import Controller from './Controller';
import PostService from "./../services/PostService";
import RecordService from "./../services/RecordService";
import Post from "./../models/Post";
import Record from "./../models/Record";
const postService = new PostService(
new Post().getInstance()
);

const recordService = new RecordService(
new Record().getInstance()
);

class PostController extends Controller {

constructor(service) {
super(service);
}

async insert(req, res)
{
console.log("usando overwrite");
let response = await this.postService.insert(req.body);
console.log("creando record");
let response2 = await this.recordService.insert({"title":response.slung,"status":"created","post": response });
console.log("creado "+response2);
if (response.error) return res.status(response.statusCode).send(response);
return res.status(201).send(response);
}
}

export default new PostController(postService);

Collapse
 
adrianhelvik profile image
Adrian

Bug: Failing to unlink a file will cause an unhandled rejection.

await new Promise((resolve, reject) => {
  fs.unlink(path, err => {
    if (err) reject(err)
    else resolve()
  })
})

Now the unlinking is awaited as well.

Additionally you don't need the Connection class. Just write the code at the top level.

Collapse
 
pacheco profile image
Thiago Pacheco

Thank you for reporting that Adrian, actually this part wasn't supposed to be in the code.
I was going to add a file upload functionality too but I realized it would make this post very long and I forgot to remove it all.
And thank you for the solution too, I am going to add this in the next functionalities :)

Collapse
 
yuriytigiev profile image
YuriyTigiev

Why babel modules required for developing API?

Collapse
 
pacheco profile image
Thiago Pacheco

It is not required.
I have set it up in this project to be able to use ES6 syntax.

Collapse
 
yuriytigiev profile image
YuriyTigiev

Which version of a nodejs do you use?

Collapse
 
sunilksamanta profile image
Sunil Kumar Samanta • Edited

Hey, This is really a very good structure. I've made my version following this tutorial.
Also included some basic modules like Media, Auth, and modified a little bit. Fixed some issues.
Here is the link for my article.
medium.com/@sunilksamanta/rest-api...

Collapse
 
pacheco profile image
Thiago Pacheco

Wow, that is super nice Sunil!
Great job on the implementation and all the extra functionality you created!

 
pacheco profile image
Thiago Pacheco

First: This is not an error, this is an extra function (only in the repo) that you can use to find a single item but I did not cover in this article because it would get very long, so I decided to focus only on the main functions.

Second: Also not an error, this helpers folder is where you can continue your project adding helper functions as you would like, the purpose of the entire article is to recommend a clear structure to start a new project.

third: You might have nodemon installed globally to run this, you can do it by running the command: npm install -g nodemon.
I will add that to the post to make it clear.

I hope it might help you, Rodrigo.

Se precisar de algo fico feliz em ajudar! :)

Collapse
 
sannajammeh5 profile image
Sanna Jammeh • Edited

I think express includes bodyParser by default as of 4.16.

Collapse
 
pacheco profile image
Thiago Pacheco

That is true Sanna, I didn't know that. I am going to run some tests and I am probably going to change these lines on the post. Thank you!

Collapse
 
mssi90 profile image
mssi90

Would you use a service for each model? How could it be done when there are several models?

Collapse
 
pacheco profile image
Thiago Pacheco

Yes, in this architecture I chose to use a service for each model.
If you have a very large application with a lot of models, then what I would recommend is to use a microservices architecture.

That could be done having various micro applications like this one, each running on a separate docker container. This actually is a content I am working on for the next post.

Collapse
 
mssi90 profile image
mssi90

Great! Thanks for the recommendation.

Regards.

Collapse
 
ngcammayo profile image
Norberto Cammayo

Very good architecture to start with. You are awesome Sir.

Collapse
 
haposan06 profile image
Johannes Haposan Napitupulu

Nice Article! Anw can you give reason which approach is better when using Class? Is it the export new Class or just make every function static? This is for the case Service and Controllers

Collapse
 
pacheco profile image
Thiago Pacheco

Thank you Johannes, actually using static functions would be even better in these cases. I am thinking about changing the post with that.

Thank you for coming up with that! :)

Collapse
 
troywolf profile image
Troy Wolf

Your article got me from zero to a clean, easy-to-understand API in 10 minutes. Thank you!

Collapse
 
pacheco profile image
Thiago Pacheco

I am so glad to read that, thank you Troy!

Collapse
 
slidenerd profile image
slidenerd

this works for something simple but most certainly not when you have different database needs for each model and your controllers also aggregate stuff

Collapse
 
mausomau profile image
Mauricio

Excellent content¡¡ I am starting to study backend more seriously and I find your article very clear and simple, even though is a difficult topic (at least for me)

Collapse
 
pacheco profile image
Thiago Pacheco

Thank you very much Mauricio.

Let me know if you have any questions :)

Collapse
 
nahidmbstu profile image
Nahid Hasan

Why do i need this structure ? i use normal functional approach.

Collapse
 
pacheco profile image
Thiago Pacheco

Yes, I've being using a normal function approach for a long time too, but applying design patterns using class is really helpful and easy to replicate/add functionalities across your app, this will make your life easier especially on a big project.
For example, if you need to create a new endpoints with the same common functionalities as a CRUD like we did there you won't need to copy and paste code (respecting CLEAN code), you can just inherit all the functionality you have already created somewhere.

Collapse
 
lechip profile image
Oscar Barrios

neither functional nor OO are normal. Those paradigms merely tools. The author chose to go OO but doesn't mean that a lot of the concepts can be used in the functional way.

Collapse
 
mgurtd profile image
Marc Gurt

I used your arch proposal to build my new project API. It's working great!!

Now, for new features it's simple to scale ;)

Thank you for sharing, Thiago.

Collapse
 
pacheco profile image
Thiago Pacheco

Wow, really Marc!?
I am so glad to know that, thank you for sharing your experience with it!

Collapse
 
mgurtd profile image
Marc Gurt

Hi Thiago !

A made a pull request to the repo with some suggestions.

Thank you for share ,)

Thread Thread
 
pacheco profile image
Thiago Pacheco

Hey Marc, thank you for your collaboration!
I will have to update the article in order to merge your PR into the main branch, so I will do that once I have more time in here.
Nice implementation by the way with the 404 validation and the countDocuments update!

Collapse
 
yaaagy profile image
Yogesh Sharma

Nothing more to add. Great post! You can address server.js in this post. You have mixed up variable name (using "app" instead of "server")

Collapse
 
pacheco profile image
Thiago Pacheco

OMG you are right Yogesh! Thank you for reporting that. I already updated the post :)

Collapse
 
manojnegi profile image
manoj-negi

Perfect.

Collapse
 
diegoalejandrogomez profile image
Diego Alejandro Gomez

This is really helpful. I am a c++ engineer who suddenly had to do an api in node and this looks really practical. Good job and thank you!

Collapse
 
pacheco profile image
Thiago Pacheco

Thank you for the feedback Diego, I am super glad it helped you!

Collapse
 
yrgamit profile image
Yrga well

What a great description thanks alot.

Collapse
 
diegoalejandrogomez profile image
Diego Alejandro Gomez

Btw, I am pretty surprised how toxic the community is. I would love to see code from those who talk about architecture

Collapse
 
pacheco profile image
Thiago Pacheco

Hi Rodrigo, I just downloaded the source code again, tested it and everything looks fine.
Can you tell me which problems you ran through, maybe I can help you.

Collapse
 
lalwanikamaldev profile image
lalwanikamaldev

HI Thiago ,

I am new to node js and UI development and i am using this architecture for development .

I am stuck to joining 2 table and get the result set . let me know what is the right way to join the table and get the result set from it .
i have 2 table post and comment where comment is having relationship with post .
Looking forward for your reply ...

Thread Thread
 
lalwanikamaldev profile image
lalwanikamaldev

Any Idea ?

Thread Thread
 
pacheco profile image
Thiago Pacheco

Hello there,
Sorry for the delay, I thought I had answer you already.

You could basically define a comments attribute into your post schema with a reference for this comment schema, like so:

{
 ...your attributes
  comments: [{ type: Schema.Types.ObjectId, ref: 'Comment' }]
});
Enter fullscreen mode Exit fullscreen mode

Then when you query this post, you can use the populate method from mongoose to retrieve the comments.

Post.
  findOne({ title: 'title' }).
  populate('comments')
Enter fullscreen mode Exit fullscreen mode

Here is the link with the docs about how you can implement a relationship one-to-many.
mongoosejs.com/docs/populate.html

Thread Thread
 
pacheco profile image
Thiago Pacheco

But remember that this is not the same concept of joining as in SQL, in MongoDB you don't have this concept of relationship.
The relationship is defined by code, with your schemas and the extra validations you are supposed to create based on your necessities.

Here is some more information about the key differences between NoSQL and SQL:
mongodb.com/nosql-explained/nosql-...

Collapse
 
mzcoderhub profile image
Galang YP

I just thinking, i can be pros when read this post

Collapse
 
allstackdev profile image
AllStackDev 👨‍💻 • Edited

So I am trying to get the model instance here, but I keep getting cannot overwrite feeds(a model I have) model once compiled. Any help!

Collapse
 
pacheco profile image
Thiago Pacheco • Edited

I ran some tests some time ago and I realize that when you try to get the model multiple times this error could show up because we try to reinitialize the model.

So a quick fix for that is to add a custom function to your model to get only the model and not reinitialize all of it, like the example below:

getModel() {
return mongoose.model("your model name");
}

Let me know if that solves your problem. :)

Collapse
 
allstackdev profile image
AllStackDev 👨‍💻

Yes wanted to do this but realized that it will be repeatation of code so I added a functionality to the
getInstance (init =true) {
init && initScheme();
return mongoose.model("your model name");
}

Thread Thread
 
pacheco profile image
Thiago Pacheco

This is way better, nice job man!
I am going to change the structure of this model soon.