Phần 2: Xây dựng backend với FeatherJS

Ngày 28 tháng 12 năm 2018 | 173 views

Chào các bạn, ở phần này chúng ta sẽ tiến hành xây dựng các service, API, Socket...

Chúng ta sẽ sử dụng MongoDB để lưu trữ và Mongoose để map object. Vì bài viết sẽ chỉ tập trung vào FeathersJS nên các bạn tự tìm hiểu MongoDB và Mongoose nhé. Trước tiên sẽ tạo 1 DB tên là feathers và tạo ra 2 collection: users và messages nhé. Các bạn có thể sử dụng các tool như MongoCompass, Studio 3T...hoặc command.

OK, giờ bắt tay vào tạo project backend nào!

Coi như mình đã cài đặt featherjs nhé. Đầu tiên cần tạo 1 thư mục chứa project và chạy lệnh để feathers generate ra project:

feathers generate app

Feathers sẽ hỏi và cung cấp một vài lựa chọn cho bạn. Đại loại như tên project, folder chưa source, sử dụng package manager nào (npm hay yarn)? loại API và framework test nào...

Sau đó thì mọi việc sẽ được generate tự động. Hay nữa là nó sẽ tự động tích hợp ExpressJS vào trong luôn. Perfect! Tuyệt vời! ^^

Cấu trúc project sẽ gồm các mục sau:

  • config/ - Chứa các file config cho ứng dụng. File production.json sẽ ghi đè file default.json khi deploy với setting NODE_ENV=production
  • node_modules/ - Chứ tất tật các thư viện
  • public/ - Chứa các file tĩnh của server (ảnh ọt các thứ), file index.html (thứ sẽ show khi gõ đường dẫn tới server vào trình duyệt, tất nhiên là sau khi chạy server)
  • src/ -Thư mục chưa source code
  • test/ - Chứa file Mocha test cho app, hooks và các service
  • package.json Chứa thông tin các thư viện, package của project. Nếu bác nào code java thì có thể hiểu nó là file pom.xml trong maven ấy

Tạm thời những file khác chưa cần quan tâm tới. :D. Và sau khi generate project xong, mình sẽ chạy thử xem hình dáng nó ra sao nhé!

npm start

Các bạn có thể dùng IDE hay Editor nào code cũng đc. Mềnh thì quen dùng Visual studio code Insider, vừa nhẹ vừa mạnh.

Server sẽ run ở port 3030. Khi gõ localhost:3030 vào trình duyệt, file index.html sẽ hiển thị như dưới:

OK, giờ tạm thời Ctr + C để tắt server đi. Bước tiếp theo chúng ta cần 1 service messages để thao tác CRUD (thêm, request, sửa, xóa) các message khi chat. Chỉ cần chạy lệnh và feather sẽ tự động tạo các file cần thiết và code khung sẵn <Phê lòi :3>

feathers generate service

Tương tự như quá trình generate project, chúng ta sẽ có vài sự lựa chọn. Vì mình dùng MongoDB và mongoose nên mình sẽ chọn Mongoose, các bạn dùng DB khác thì chọn theo list gợi ý nhé!

*Chú ý: Đoạn điền tên database connection cần điền chuẩn tên database trong DB của bạn nhé!

Như vậy sau khi generate chúng ta sẽ có các file mới:

  • src/services/messages/messages.service.js - File setup và config service vừa tạo
  • src/services/messages/messages.hooks.js - File hook được config cho service message vừa tạo.
  • src/models/messages.model.js - Model Message sẽ được map tự động vs MongoDB thông qua Mongoose
  • test/services/messages.test.js - File mocha test cho service vừa tạo

Gõ npm start và run server. Sử dụng postman để thử call API. Như vậy chẳng cần viết dòng code nào chúng ta đã tạo ra 1 service CRUD với object tự động map với MongoDB. Siêu đơn giản đúng không! :3

Bước tiếp theo chúng ta cần tạo ra service và model map với User để còn tạo user và đăng nhập, đồng thời feathers cũng cung cấp sẵn plugin authenticate. Chạy lệnh sau:

feathers generate authentication

Và điền vào các phần yêu cầu nhập. Ở đây mình sẽ dùng username + password để sử dụng dữ liệu trong bảng users để đăng nhập; và tất nhiên mình cũng dùng mongoose để map object rồi!

Mô hình authentication ở đây không có gì lạ cả:

Tiếp theo chúng ta sẽ chạy và tạo ra 1 vài user bằng API. Lưu ý: nếu bạn đã có dữ liệu cũ trong collection users thì xóa hết đi và tạo bằng API vì nó có thể dần tới lỗi. Nhưng trước đó mình sẽ thêm 1 trường avatar cho user để sau này chat phải có cái hình cho nó sinh động. User model của chúng ta sẽ như sau:

// users-model.js - A mongoose model
//
module.exports = function (app) {
  const mongooseClient = app.get('mongooseClient');
  const users = new mongooseClient.Schema({
    email: {type: String, unique: true, lowercase: true},
    password: { type: String },
    avatar: { type: String},
  }, {
    timestamps: true
  });

  return mongooseClient.model('users', users);
};

Tạo user bằng postman, các bạn có thể tạo bao nhiêu user tùy nhé. Phần avatar thì sau này làm frontend sẽ tính tới :D

Và call thử API get về tất cả user xem sao.

Vì trong file user.hook chúng ta có đoạn sau nên bắt buộc phải authen trước mới gọi được các API find, get, update, patch..

before: {
    all: [],
    find: [ authenticate('jwt') ],
    get: [ authenticate('jwt') ],
    create: [ hashPassword() ],
    update: [ hashPassword(),  authenticate('jwt') ],
    patch: [ hashPassword(),  authenticate('jwt') ],
    remove: [ authenticate('jwt') ]
  },

Vì vậy, chúng ta sẽ phải gọi API /authenticate để lấy token đã

Sau đó sử dụng token để call /users

Bây giờ trước khi một message được tạo, chúng ta cần add thuộc tính userID để xác định message đó là của user nào. Để làm được thì chúng ta cần tạo ra 1 hook cho service message

feathers generate hook

Nội dung code của process-message:

module.exports = function (options = {}) { // eslint-disable-line no-unused-vars
  return async context => {
    const { data } = context;

    // Throw an error if we didn't get a text
    if(!data.text) {
      throw new Error('A message must have a text');
    }

    // The authenticated user
    const user = context.params.user;
    // The actual message text
    const text = context.data.text
      // Messages can't be longer than 400 characters
      .substring(0, 400);

    // Override the original data (so that people can't submit additional stuff)
    context.data = {
      text,
      // Set the user id
      userId: user._id,
      // Add the current date
      createdAt: new Date().getTime()
    };

    // Best practice: hooks should always return the context
    return context;
  };
};

Process-message sẽ làm 3 bước:

- Kiểm tra nếu text trong data không có sẽ throw ra exception

- Cắt chuỗi để giới hạn độ dài message là 400 ký tự

- Update dữ liệu message: message sau khi truncate, thêm userId để biết ai là người gửi, thêm thời gian tạo ra message

Ok, vậy là sắp xong rồi đó. Chúng ta sẽ làm một bước cuối cùng. 

Như ở trên mỗi lần tạo 1 message chúng ta sẽ có userId trong đó. Bây giờ cần lấy ra thông tin user từ userId đó tương ứng vs từng message. Chúng ta sẽ tạo 1 hook đặt tên nó là populate-user

module.exports = function (options = {}) { // eslint-disable-line no-unused-vars
  return async context => {
    // Get `app`, `method`, `params` and `result` from the hook context
    const { app, method, result, params } = context;

    // Make sure that we always have a list of messages either by wrapping
    // a single message into an array or by getting the `data` from the `find` method's result
    const messages = method === 'find' ? result.data : [ result ];

    // Asynchronously get user object from each message's `userId`
    // and add it to the message
    await Promise.all(messages.map(async message => {
      // Also pass the original `params` to the service call
      // so that it has the same information available (e.g. who is requesting it)
      message.user = await app.service('users').get(message.userId, params);
    }));

    // Best practice: hooks should always return the context
    return context;
  };
};

Để kiểm tra thì các bạn có thể dùng postman để call thử nhé! Như vậy là chúng ta đã hoàn thành backend. Ở phần sau mình sẽ sử dụng Angular để xây dựng frontend! :D

Phần 3: Xây dựng frontend với Angular 6

Ngày 03 tháng 1 năm 2019

03-01-2019
154
Tìm hiểu về Node Js cơ bản

Ngày 03 tháng 9 năm 2017

03-09-2017
297