• Home
  • Java
    • All
    • Các ide để lâp trình Java
    • Java-core

    Thuật toán sắp xếp Bubble sort(Sắp xếp nổi bọt)

    Sử dụng ThreadPool và Executor trong Java qua ví dụ

    Ghi Log trong java – Thực hiện như thế nào cho đúng ?

    Ghi Log trong java – Thực hiện như thế nào cho đúng ?

    Hướng dẫn cài đặt và sử dụng IntelliJ IDEA cơ bản

    Hướng dẫn cài đặt và sử dụng IntelliJ IDEA cơ bản

    Lộ trình để trở thành Java Dev

    Trending Tags

    • Spring framework
    • Hibernate-JPA
    • Database
      • All
      • Mysql
      • Oracle Database

      Select for update in Oracle

      HOW TO SETUP MYSQL 5.7 REPLICATION WITH MONITORING ON UBUNTU 16.04

      Trending Tags

    • Utility Library
      • All
      • Guava Cache
      Thực hành Cache Guava Cache trong ứng dụng Spring Boot

      Thực hành Cache Guava Cache trong ứng dụng Spring Boot

    • Tech News
    No Result
    View All Result
    • Home
    • Java
      • All
      • Các ide để lâp trình Java
      • Java-core

      Thuật toán sắp xếp Bubble sort(Sắp xếp nổi bọt)

      Sử dụng ThreadPool và Executor trong Java qua ví dụ

      Ghi Log trong java – Thực hiện như thế nào cho đúng ?

      Ghi Log trong java – Thực hiện như thế nào cho đúng ?

      Hướng dẫn cài đặt và sử dụng IntelliJ IDEA cơ bản

      Hướng dẫn cài đặt và sử dụng IntelliJ IDEA cơ bản

      Lộ trình để trở thành Java Dev

      Trending Tags

      • Spring framework
      • Hibernate-JPA
      • Database
        • All
        • Mysql
        • Oracle Database

        Select for update in Oracle

        HOW TO SETUP MYSQL 5.7 REPLICATION WITH MONITORING ON UBUNTU 16.04

        Trending Tags

      • Utility Library
        • All
        • Guava Cache
        Thực hành Cache Guava Cache trong ứng dụng Spring Boot

        Thực hành Cache Guava Cache trong ứng dụng Spring Boot

      • Tech News
      No Result
      View All Result
      Gà Tồ Study
      No Result
      View All Result
      Home Api clients

      Designing a better architecture for a Node.js API

      gatostudy by gatostudy
      13 November, 2019
      in Tech
      0
      Designing a better architecture for a Node.js API
      0
      SHARES
      126
      VIEWS
      Share on FacebookShare on Twitter

      A few time ago, a made a post about creating a Fullstack project with Node.js, React.js and MongoDB. This is a very cool starter project that could help us get up and running with the basics.

      But implementing a better architecture is very important, especially if you have a big project and you are working with a large team. This will help you develop and maintain easily your project.

      So the objective of this post is to share my current API architecture and the way I found to create a better structure, applying design patterns and clean code.

      Let’s dive in to code.

      First of all, let’s create our work folder and the initial files.

      $ mkdir node-starter
      $ cd node-starter
      $ touch index.js
      $ npm init -y
      

      Creating the strucuture

      Now, let’s create the base folders for the project

       $ mkdir config src src/controllers src/models src/services src/helpers
      

      Add dependencies

      For this project, we are going to use Express and MongoDB, so let’s add our inicial dependencies.

      $ npm install --save body-parser express mongoose mongoose-unique-validator slugify
      

      Add DEV dependencies

      As we want to be able to use the latest ES6 syntax in this project, let’s add babel and configure it.

      npm i -D @babel/node @babel/core @babel/preset-env babel-loader nodemon
      

      Here we also added nodemon as a dev dependency to run and test the project easily.

      Setting up babel

      In the main folder, create a file called .babelrc with the following code:

      {
        "presets": [
          "@babel/preset-env"
        ]
      }
      

      Now go over to your package.json and add the following scripts

      "scripts": {
          "start": "babel-node index.js",
          "dev:start": "clear; nodemon --exec babel-node index.js"
       }
      

      Create the server

      Under config folder, create a file called server.js with the following code

      import express from "express";
      import bodyParser from "body-parser";
      const app = express();
      
      app.use(bodyParser.json());
      
      export default app;
      

      Now let’s import our server config into our index.js file:

      import server from './config/server';
      
      const PORT = process.env.PORT || 5000;
      server.listen(PORT, () => {
        console.log(`app running on port ${PORT}`);
      });
      

      At this point, you should be able to run your server with the following script:

      $ npm run dev:start
      

      And you should get a response like this:

      [nodemon] 1.19.4
      [nodemon] to restart at any time, enter `rs`
      [nodemon] watching dir(s): *.*
      [nodemon] watching extensions: js,mjs,json
      [nodemon] starting `babel-node index.js`
      app running on port 5000
      

      Setting up the Database

      Now let’s set up our database.
      For this, you must have mongoDB up and running in your local machine.

      Under config, add the file database.js

      //database.js
      
      import mongoose from "mongoose";
      
      class Connection {
        constructor() {
          const url =
            process.env.MONGODB_URI || `mongodb://localhost:27017/node-starter`;
          console.log("Establish new connection with url", url);
          mongoose.Promise = global.Promise;
          mongoose.set("useNewUrlParser", true);
          mongoose.set("useFindAndModify", false);
          mongoose.set("useCreateIndex", true);
          mongoose.set("useUnifiedTopology", true);
          mongoose.connect(url);
        }
      }
      
      export default new Connection();
      
      
      

      Here we are creating a singleton instance of our database by exporting a new Connection. This is automatically handled by node when you export it like this and it makes sure that you are only going to have one single instance of this class in your application.

      And now, import it right in the beginning of your index.js file.

      //index.js
      import './config/database';
      //...
      

      Create a model

      Now let’s create our first model.
      Under src/models, create a file called Post.js with the following content.

      //src/models/Post.js
      import mongoose, { Schema } from "mongoose";
      import uniqueValidator from "mongoose-unique-validator";
      import slugify from 'slugify';
      
      class Post {
      
        initSchema() {
          const schema = new Schema({
            title: {
              type: String,
              required: true,
            },
            slug: String,
            subtitle: {
              type: String,
              required: false,
            },
            description: {
              type: String,
              required: false,
            },
            content: {
              type: String,
              required: true,
            }
          }, { timestamps: true });
          schema.pre(
            "save",
            function(next) {
              let post = this;
              if (!post.isModified("title")) {
                return next();
              }
              post.slug = slugify(post.title, "_");
              console.log('set slug', post.slug);
              return next();
            },
            function(err) {
              next(err);
            }
          );
          schema.plugin(uniqueValidator);
          mongoose.model("posts", schema);
        }
      
        getInstance() {
          this.initSchema();
          return mongoose.model("posts");
        }
      }
      
      export default Post;
      
      

      Create our services

      Let’s create a Service class that is going to have all the common functionalities for our API, making it available for other services to inherit them.
      Create a file Service.js under src/services folder;

      //src/services/Service.js
      
      import mongoose from "mongoose";
      
      class Service {
        constructor(model) {
          this.model = model;
          this.getAll = this.getAll.bind(this);
          this.insert = this.insert.bind(this);
          this.update = this.update.bind(this);
          this.delete = this.delete.bind(this);
        }
      
        async getAll(query) {
          let { skip, limit } = query;
      
          skip = skip ? Number(skip) : 0;
          limit = limit ? Number(limit) : 10;
      
          delete query.skip;
          delete query.limit;
      
          if (query._id) {
            try {
              query._id = new mongoose.mongo.ObjectId(query._id);
            } catch (error) {
              console.log("not able to generate mongoose id with content", query._id);
            }
          }
      
          try {
            let items = await this.model
              .find(query)
              .skip(skip)
              .limit(limit);
            let total = await this.model.count();
      
            return {
              error: false,
              statusCode: 200,
              data: items,
              total
            };
          } catch (errors) {
            return {
              error: true,
              statusCode: 500,
              errors
            };
          }
        }
      
        async insert(data) {
          try {
            let item = await this.model.create(data);
            if (item)
              return {
                error: false,
                item
              };
          } catch (error) {
            console.log("error", error);
            return {
              error: true,
              statusCode: 500,
              message: error.errmsg || "Not able to create item",
              errors: error.errors
            };
          }
        }
      
        async update(id, data) {
          try {
            let item = await this.model.findByIdAndUpdate(id, data, { new: true });
            return {
              error: false,
              statusCode: 202,
              item
            };
          } catch (error) {
            return {
              error: true,
              statusCode: 500,
              error
            };
          }
        }
      
        async delete(id) {
          try {
            let item = await this.model.findByIdAndDelete(id);
            if (!item)
              return {
                error: true,
                statusCode: 404,
                message: "item not found"
              };
      
            console.log("removed item", item);
      
            if (item.path) {
              console.log("unlink item", item.path);
              fs.unlink(item.path, function(err) {
                if (err) {
                  console.log("error deleting file");
                  throw err;
                }
                console.log("File deleted!");
              });
            }
      
            return {
              error: false,
              deleted: true,
              statusCode: 202,
              item
            };
          } catch (error) {
            return {
              error: true,
              statusCode: 500,
              error
            };
          }
        }
      }
      
      export default Service;
      
      

      Ok, this seems a lot of code.

      In this service, we created the main functionality (a basic CRUD) for our application, adding functions to get, insert, update and delete items.

      Now, let’s create our Post service and inherit all this functionality we just created.
      Under src/services, create a file PostService.js with the following content:

      //src/services/PostService
      import Service from './Service';
      
      class PostService extends Service {
        constructor(model) {
          super(model);
        }
      };
      
      export default PostService;
      
      
      

      It is as simple as that, it inherits all the functionality we created in our main Service.js file and it can be repeated across your API for all the other endpoints.

      Create the controllers

      We are going to follow the same principle we had while creating our services, here we are going to create a main Controller.js file that will have all the common functionalities and make the other controllers inherit it.

      Create a file Controller.js under src/controllers and add the following code:

      //src/controllers/Controller.js
      
      class Controller {
      
        constructor(service) {
          this.service = service;
          this.getAll = this.getAll.bind(this);
          this.get = this.get.bind(this);
          this.insert = this.insert.bind(this);
          this.update = this.update.bind(this);
          this.delete = this.delete.bind(this);
        }
      
        async getAll(req, res) {
          return res.status(200).send(await this.service.getAll(req.query));
        }
      
        async get(req, res) {
          let response = await this.service.get(req.params)
          return res.status(response.statusCode).send(response);
        }
      
        async insert(req, res) {
          let response = await this.service.insert(req.body);
          if (response.error) return res.status(response.statusCode).send(response);
          return res.status(201).send(response);
        }
      
        async update(req, res) {
          const { id } = req.params;
      
          let response = await this.service.update(id, req.body);
      
          return res.status(response.statusCode).send(response);
        }
      
        async delete(req, res) {
          const { id } = req.params;
      
          let response = await this.service.delete(id);
      
          return res.status(response.statusCode).send(response);
        }
      
      }
      
      export default Controller;
      

      Now, let’s create a PostController file under src/controllers

      //src/controllers/PostController.js
      
      import Controller from  './Controller';
      import PostService from  "./../services/PostService";
      import Post from  "./../models/Post";
      const postService = new PostService(
        new Post().getInstance()
      );
      
      class PostController extends Controller {
      
        constructor(service) {
          super(service);
        }
      
      }
      
      export default new PostController(postService);
      
      

      Here, we are importing the desired service and model and we are also creating an instance of our Post service passing a Post model instance to its constructor.

      Create the routes

      Now it’s time to create the routes for our API.

      Under config folder, create a file routes.js

      //config/routes.js
      import PostController from './../src/controllers/PostController';
      
      export default (app) => {
      
        // POST ROUTES
        app.get(`/api/post`, PostController.getAll);
        app.get(`/api/post/:params`, PostController.get);
        app.post(`/api/post`, PostController.insert)
        app.put(`/api/post/:id`, PostController.update);
        app.delete(`/api/post/:id`, PostController.delete);
      
      }
      

      This file imports the Post controller and map the functions to the desired routes.

      Now we have to import our routes into our server.js file right after our body parser setup, like so:

      //config/server.js
      //...
      import setRoutes from "./routes";
      setRoutes(server);
      //...
      

      Et Voila!!

      At this point, you should be able to make requests to all the routes created, so let’s test it.

      Make a POST request for the route /api/post with the following json body:
      Here you can use an API client like Postman or Insomnia to this task

      {
          "title": "post 1",
          "subtitle": "subtitle post 1",
          "content": "content post 1"
      }
      

      You should get something like this:

      {
        "error": false,
        "item": {
          "_id": "5dbdea2e188d860cf3bd07d1",
          "title": "post 1",
          "subtitle": "subtitle post 1",
          "content": "content post 1",
          "createdAt": "2019-11-02T20:42:22.339Z",
          "updatedAt": "2019-11-02T20:42:22.339Z",
          "slug": "post_1",
          "__v": 0
        }
      }
      

      Conclusion

      There are lots of ways to design the architecture of an API, and the goal is always to have a cleaner and reusable code, don’t repeating yourself and helping others to work along easily, plus it will also help yourself with maintenance and adding new functionalities.

      You can find the source code here

      Hope you may find it useful.
      Bye!

      Tags: nodejs
      Previous Post

      Sử dụng HashMap trong java

      Next Post

      Spring Data JPA - Giới thiệu các tính năng và cách sử dụng

      Next Post
      Spring Data JPA – Giới thiệu các tính năng và cách sử dụng

      Spring Data JPA - Giới thiệu các tính năng và cách sử dụng

      Leave a Reply Cancel reply

      • Trending
      • Comments
      • Latest
      Hướng dẫn cài đặt và sử dụng IntelliJ IDEA cơ bản

      Hướng dẫn cài đặt và sử dụng IntelliJ IDEA cơ bản

      24 June, 2020
      Spring Data JPA – Giới thiệu các tính năng và cách sử dụng

      Spring Data JPA – Giới thiệu các tính năng và cách sử dụng

      13 November, 2019
      Ghi Log trong java – Thực hiện như thế nào cho đúng ?

      Ghi Log trong java – Thực hiện như thế nào cho đúng ?

      29 November, 2019
      Hướng dẫn kết hợp Spring Data JPA với SpringBoot qua ví dụ

      Hướng dẫn kết hợp Spring Data JPA với SpringBoot qua ví dụ

      14 November, 2019

      Sử dụng ThreadPool và Executor trong Java qua ví dụ

      1
      Hướng dẫn cài đặt và sử dụng IntelliJ IDEA cơ bản

      Hướng dẫn cài đặt và sử dụng IntelliJ IDEA cơ bản

      1

      Select for update in Oracle

      0
      Designing a better architecture for a Node.js API

      Designing a better architecture for a Node.js API

      0

      Select for update in Oracle

      23 January, 2022

      HOW TO SETUP MYSQL 5.7 REPLICATION WITH MONITORING ON UBUNTU 16.04

      3 October, 2021

      20 May, 2021

      Giải thuật tìm kiếm nhị phân- Binary Search

      10 March, 2020

      Bài Viết Phổ Biến

      • Hướng dẫn cài đặt và sử dụng IntelliJ IDEA cơ bản

        Hướng dẫn cài đặt và sử dụng IntelliJ IDEA cơ bản

        0 shares
        Share 0 Tweet 0
      • Spring Data JPA – Giới thiệu các tính năng và cách sử dụng

        0 shares
        Share 0 Tweet 0
      • Ghi Log trong java – Thực hiện như thế nào cho đúng ?

        0 shares
        Share 0 Tweet 0
      • Hướng dẫn kết hợp Spring Data JPA với SpringBoot qua ví dụ

        0 shares
        Share 0 Tweet 0
      • Thực hành Cache Guava Cache trong ứng dụng Spring Boot

        0 shares
        Share 0 Tweet 0

      Theo dõi blog qua email

      Nhập địa chỉ email của bạn để đăng ký theo dõi và nhận thông báo về các bài mới qua email.

      Join 2 other subscribers

      Gà Tồ Study

      © 2019 Gato Study - Vì một Việt Nam tươi đẹp .

      Keep Camp & Keep Coding

      • About
      • Advertise
      • Careers
      • Contact

      Follow Us

      No Result
      View All Result
      • Home
      • Hibernate-JPA
      • Spring framework
      • Java
      • Api clients
      • Utility Library
      • Guava Cache
      • Apache commons
      • Oracle Database
      • Database
      • Mysql
      • Tech
      • MongoDB
      • Food

      © 2019 Gato Study - Vì một Việt Nam tươi đẹp .