# Ví dụ Tạo Restfull API - CRUD đơn giản với NodeJS, GraphQL (ok)

## Example 1&#x20;

<figure><img src="https://2726517656-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-M1E4Gk2ppVKb4olmnun%2Fuploads%2FfGD44UY1DgrY2UzUvqgV%2Fimage.png?alt=media&#x26;token=f58a85da-3624-4850-85cd-bb15500ba176" alt=""><figcaption></figcaption></figure>

Đọc thêm bài viết <https://javascriptuse.gitbook.io/project/advanced/quick-start-graphql> cũng xây dựng Grapql

{% file src="<https://2726517656-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-M1E4Gk2ppVKb4olmnun%2Fuploads%2FkHWLkONXplubnBR68JPb%2Fgraph.zip?alt=media&token=d3f27935-d60b-4834-9f06-f6d7cab44f74>" %}

package.json

```json
{
  "name": "graph",
  "version": "1.0.0",
  "main": "server.ts",
  "scripts": {
    "dev": "nodemon server.ts",
    "start": "nodemon app.ts"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "type": "commonjs",
  "description": "",
  "dependencies": {
    "@types/express": "^5.0.1",
    "@types/graphql": "^14.2.3",
    "@types/lodash": "^4.17.16",
    "@types/node": "^22.15.2",
    "apollo-server-express": "^3.13.0",
    "express": "^5.1.0",
    "express-graphql": "^0.12.0",
    "graphql": "^15.10.1",
    "lodash": "^4.17.21",
    "mysql2": "^3.14.1",
    "nodemon": "^3.1.10",
    "sequelize": "^6.37.7",
    "type-graphql": "^2.0.0-rc.2"
  }
}

```

src\database.ts

```typescript
'use strict';
import { Sequelize } from 'sequelize';
interface Database {
  [key: string]: any;
  sequelize: Sequelize;
}
const createDatabase = async (): Promise<Database> => {
  const database: Database = { sequelize: null! }; // Using non-null assertion since we'll assign it later
  const sequelize = new Sequelize(
    process.env.DB_NAME || 'nodejs_api',
    process.env.DB_USER || 'root',
    process.env.DB_PASS || '',
    {
      host: process.env.DB_HOST || 'localhost',
      port: process.env.DB_PORT ? parseInt(process.env.DB_PORT) : 3306,
      dialect: 'mysql',
      define: {
        freezeTableName: true,
      },
      pool: {
        max: 5,
        min: 0,
        acquire: 30000,
        idle: 10000,
      },
    });
  // Import and initialize models
  const modelModules = await Promise.all([
    import('./models/products')
  ]);
  modelModules.forEach(model => {
    const seqModel = model.default(sequelize, Sequelize);
    database[seqModel.name] = seqModel;
  });
  // Apply associations
  Object.keys(database).forEach(key => {
    if (key === 'sequelize') return;
    if (
      database[key] &&
      typeof database[key] === 'object' &&
      'associate' in database[key]
    ) {
      database[key].associate(database);
    }
  });
  database.sequelize = sequelize;
  return database;
};
// Export a promise that resolves to the database object
export default createDatabase();
```

src\models\products.ts

```typescript
export default function(sequelize: any, DataTypes: any) {
  return sequelize.define('products', {
    id: {
      type: DataTypes.INTEGER(11).UNSIGNED,
      allowNull: false,
      primaryKey: true,
      autoIncrement: true
    },
    name: {
      type: DataTypes.STRING(256),
      allowNull: false
    },
    color: {
      type: DataTypes.STRING(256),
      allowNull: false
    },
    price: {
      type: DataTypes.STRING(256),
      allowNull: false
    }
  }, {
    tableName: 'products',
    timestamps: false
  });
};
```

src\graphql\products.ts

```typescript
import { gql } from 'apollo-server-express';
import databasePromise from './../database';
const typeDefs = gql `
  type Query {
    products: [Product]
    product(id: ID!): Product
  }
  type Product {
    id: ID!
    name: String!
    color: String!
    price: String!
  }
  type Mutation {
    updateProduct(
      id: Int, 
      name: String, 
      color: String, 
      price: String
    ): String
    createProduct(
      name: String, 
      color: String, 
      price: String
    ): String
    deleteProduct(id: ID!): Boolean
  }
`
const resolvers = {
  Query: {
    products: async () => {
      const database = await databasePromise;
      return database.products.findAll();
    },
    product: async (obj: any, args: any, context: any, info: any) => {
      const database = await databasePromise;
      return database.products.findByPk(args.id);
    },
  },
  Mutation: {
    createProduct: async (root: any, args: any, context: any, info: any) => {
      const database = await databasePromise;
      const product = await database.products.create({
        name: args.name,
        color: args.color,
        price: args.price,
      });
      return product.id;
    },
    updateProduct: async (root: any, args: any, context: any, info: any) => {
      if (!args.id) return;
      const database = await databasePromise;
      let product = await database.products.update({
        name: args.name,
        color: args.color,
        price: args.price,
      }, {
        where: { id: args.id }
      });
      return 'Update Success!';
    },
    deleteProduct: async (root: any, args: any, context: any, info: any) => {
      if (!args.id) return;
      const database = await databasePromise;
      const product = await database.products.destroy({
        where: {
          id: args.id
        }
      })
      return 'Delete success!';
    }
  }
}
export { typeDefs, resolvers }
```

Oparation

```graphql
query ExampleQuery1 {
  products {
    id
    name
    price
  }
}
query ExampleQuery2 
{
  product(id: 1){
    name,
    color,
    price
  }
}
mutation {
 createProduct(
   name: "iPhone 11",
   color: "Black",
   price: "14000000",
 )
}
mutation {
 updateProduct(
   id: 1,
   name: "iPhone X",
   color: "White",
   price: "20000000",
 )
}
mutation {
 deleteProduct(
   id: 9
 )
}
```

## Example 2

C:\Users\Administrator\Desktop\mysql\_demo\nodejs\_api.sql

{% file src="<https://2726517656-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-M1E4Gk2ppVKb4olmnun%2F-MVo9SiMiEhg2LP8Ulq4%2F-MVoAXbZZHxCpXR19Cr-%2Fnodejs_api.sql?alt=media&token=7a471da8-a68d-4df4-8fa4-3870b9e54669>" %}

![](https://2726517656-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-M1E4Gk2ppVKb4olmnun%2F-MUHwwI6sUI75beo4uP_%2F-MUI10y7qGtdgQtlomFI%2FScreenshot_3.jpg?alt=media\&token=10f39495-4518-4801-87ae-e830ced5f78d)

```
{
  products {
    id,
    name,
    color,
    price
  }
}
```

C:\mysql\_demo\package.json

<pre><code>{
  "name": "grapql-crud",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "start": "node app.js"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "apollo-server-express": "^2.17.0",
<strong>    "dotenv": "^8.2.0",
</strong>    "express": "^4.17.1",
    "graphql": "^15.5.0",
    "mysql2": "^2.2.5",
    "sequelize": "^6.3.5"
  }
}
</code></pre>

C:\mysql\_demo\\.env

```
DB_HOST="localhost"
DB_USER="root"
DB_PASS=""
DB_NAME="nodejs_api"
DB_PORT=3306
```

C:\mysql\_demo\models\products.js

```
module.exports = function(sequelize, DataTypes) {
  return sequelize.define('products', {
    id: {
      type: DataTypes.INTEGER(11).UNSIGNED,
      allowNull: false,
      primaryKey: true,
      autoIncrement: true
    },
    name: {
      type: DataTypes.STRING(256),
      allowNull: false
    },
    color: {
      type: DataTypes.STRING(256),
      allowNull: false
    },
    price: {
      type: DataTypes.STRING(256),
      allowNull: false
    }
  }, {
    tableName: 'products',
    timestamps: false
  });
};
```

C:\mysql\_demo\GraphQL\products.js

```
const { gql } = require('apollo-server-express');
const db = require('./../database');
const typeDefs = gql `
  type Query {
    products: [Product]
    product(id: ID!): Product
  }
  type Product {
    id: ID!
    name: String!
    color: String!
    price: String!
  }
  type Mutation {
    updateProduct(
      id: Int, 
      name: String, 
      color: String, 
      price: String
    ): String
    createProduct(
      name: String, 
      color: String, 
      price: String
    ): String
    deletProduct(id: Int): Boolean
  }
`
const resolvers = {
  Query: {
    products: async () => db.products.findAll(),
    product: async (obj, args, context, info) =>
      db.products.findByPk(args.id),
  },
  Mutation: {
    createProduct: async (root, args, context, info) => {
      product = await db.products.create({
        name: args.name,
        color: args.color,
        price: args.price,
      });
      return product.id;
    },
    updateProduct: async (root, args, context, info) => {
      if (!args.id) return;
      product = await db.products.update({
        name: args.name,
        color: args.color,
        price: args.price,
      }, {
        where: { id: args.id }
      });
      return 'Update Success!';
    },
    deleteProduct: async (root, args, context, info) => {
      if (!args.id) return;
      product = await db.products.destroy({
        where: {
          id: args.id
        }
      })
      return 'Delete success!';
    }
  }
}
module.exports = { typeDefs, resolvers }
```

![](https://2726517656-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-M1E4Gk2ppVKb4olmnun%2F-MUI2MFflOmKTNlyIQEN%2F-MUI2qOBvcOovAVQNWGC%2FScreenshot_4.jpg?alt=media\&token=3dc49cf7-960e-4c22-b0f4-3e679633a6b0)

```
{
  product(id: 2){
    name,
    color,
    price
  }
}
```

![](https://2726517656-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-M1E4Gk2ppVKb4olmnun%2F-MUI2MFflOmKTNlyIQEN%2F-MUI3E0EZ52zKfXEaOjZ%2FScreenshot_5.jpg?alt=media\&token=c6083bc6-8459-405a-861a-4f031eebc38d)

![](https://2726517656-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-M1E4Gk2ppVKb4olmnun%2F-MUI2MFflOmKTNlyIQEN%2F-MUI3NfACuKIj2W9HAoY%2FScreenshot_6.jpg?alt=media\&token=d55a8692-e74b-4ef1-90b3-28565ba541d0)

```
mutation {
 createProduct(
   name: "iPhone 11",
   color: "Black",
   price: "14000000",
 )
}
```

![](https://2726517656-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-M1E4Gk2ppVKb4olmnun%2F-MUI2MFflOmKTNlyIQEN%2F-MUI3XKaqIJFoXJbS1EQ%2FScreenshot_7.jpg?alt=media\&token=ca130068-e1e2-41bc-9c39-1ac6348835a0)

```
mutation {
 updateProduct(
   id: 1,
   name: "iPhone X",
   color: "White",
   price: "20000000",
 )
}
```

Cập nhật lại C:\mysql\_demo\GraphQL\products.js

![](https://2726517656-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-M1E4Gk2ppVKb4olmnun%2F-MUI2MFflOmKTNlyIQEN%2F-MUI6epDVHBAPD702oba%2FScreenshot_8.jpg?alt=media\&token=0d530476-daf2-44f6-a8ef-493eca54bfe7)

```
mutation {
 deleteProduct(
   id: 6
 )
}
```

## Tạo Restfull API - CRUD đơn giản với NodeJS, GraphQL

Chào mọi người,

Nối tiếp quá trình giới thiệu và nghiên cứu về GraphQL, hôm nay mình muốn làm 1 project nhỏ để học tập cũng như hướng dẫn cho mọi người về cách xây dựng ứng dụng sử dụng GraphQL và truy vấn như thế nào.

Nào cùng bắt đầu nhé!

### I. GraphQL là gì? <a href="#i-graphql-la-gi-0" id="i-graphql-la-gi-0"></a>

GraphQL là một Graph Query Language được dành cho API. Nó được phát triển bởi Facebook và hiện tại nó được duy trì bởi rất nhiều công ty lớn, và mọi cá nhân trên khắp thế giới.

GraphQL hiện tại thường được dùng cho những dự án lớn thay thế cho REST bởi sự hiệu quả, mạnh mẽ và linh hoạt hơn rất nhiều.

Mọi người có thể tham khảo tại trang chủ của GraphQL: <https://graphql.org/>

### II. Chuẩn bị <a href="#ii-chuan-bi-1" id="ii-chuan-bi-1"></a>

Với project mình sẽ demo ở đây, mình sẽ code trên NodeJS 12.

**Giả định: Máy bạn đã cài đặt NodeJS và MySQL**

#### 2.1. Tạo file `package.json` <a href="#id-21-tao-file-packagejson-2" id="id-21-tao-file-packagejson-2"></a>

`package.json` là 1 file cung cấp thông tin cần thiết cho npm, cho phép nó xác định các thự viện dùng cho dự án cũng như xử lý các phụ thuộc của dự án. File `package.json`:

```
{
  "name": "grapql-crud",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "start": "node app.js"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "apollo-server-express": "^2.17.0",
    "dotenv": "^8.2.0",
    "express": "^4.17.1",
    "mysql2": "^2.2.5",
    "sequelize": "^6.3.5"
  }
}
```

Trên đây mình đã thêm các thư viện:

1. `apollo-server-express`: Trình khởi tạo server side
2. `dotenv`: Quản lý các biến môi trường (ENV)
3. `express`: Là một khung ứng dụng web cho Node.js
4. `mysql2`: Thư viện MySQL2
5. `sequelize`: Đây là 1 trình điều khiển dùng để truy vấn cơ sở dữ liệu

Sau đó, chạy lệnh sau để tiến hành cài đặt các thư viện:

```
npm i
```

Bạn sẽ thấy 1 thư mục node\_modules được sinh ra, như vậy đã cài đặt xong thư viện.

#### 2.2 Tạo cơ sở dữ liệu sử dụng Mysql <a href="#id-22-tao-co-so-du-lieu-su-dung-mysql-3" id="id-22-tao-co-so-du-lieu-su-dung-mysql-3"></a>

```
CREATE DATABASE nodejs_api;

CREATE TABLE `products` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) DEFAULT NULL,
  `color` varchar(255) DEFAULT NULL,
  `price` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=latin1;

INSERT INTO `products` VALUES ('1', 'Iphone X', 'Black', '30000000');
INSERT INTO `products` VALUES ('2', 'Samsung S9', 'White', '24000000');
INSERT INTO `products` VALUES ('3', 'Oppo F5', 'Red', '7000000');
```

Và đây là bảng sau khi chạy sql:

![Database](https://images.viblo.asia/0d8d3006-0bd7-44e8-992c-17c6f7d44c7d.png)

#### 2.3 Tạo file kết nối Database <a href="#id-23-tao-file-ket-noi-database-4" id="id-23-tao-file-ket-noi-database-4"></a>

Trước tiên mình cần đưa các config cho database ra file .env Đây là file `.env` với nội dung:

```
DB_HOST="localhost"
DB_USER="root"
DB_PASS="root"
DB_NAME="nodejs_api
```

\*\* Lưu ý: \*\* Các bạn có thể tạo file .env.example để đưa lên git và đưa file `.env` vào `.gitignore`

Tiếp đến mình sẽ tạo file `database.js`:

```
'use strict';
const Sequelize = require('sequelize')

var db = {}

const sequelize = new Sequelize( 
  process.env.DB_NAME,  
  process.env.DB_USER, 
  process.env.DB_PASS, 
  {
    host: process.env.DB_HOST,
    port:  process.env.DB_PORT,
    dialect: 'mysql',
    define: {
        freezeTableName: true,
    },
    pool: {
        max: 5,
        min: 0,
        acquire: 30000,
        idle: 10000,
    },
    operatorsAliases: false,
})

let models = [
    require('./models/products.js')
]

// Khởi tạo models
models.forEach(model => {
    const seqModel = model(sequelize, Sequelize)
    db[seqModel.name] = seqModel
})

// Apply associations
Object.keys(db).forEach(key => {
    if ('associate' in db[key]) {
        db[key].associate(db)
    }
})

db.sequelize = sequelize

module.exports = db
```

Trong file database.js này mình sử dụng `sequelize` để tạo và quản lý database. Để tìm hiểu rõ hơn, các bạn có thể vào trang chủ của `sequelize` tại: <https://sequelize.org/master/manual/getting-started.html> Như vậy là xong phần config database, khi sử dụng bạn chỉ cần require file `database.js` vào là có 1 đối tượng db để truy vấn database rồi.

### III. Xây dựng ứng dụng <a href="#iii-xay-dung-ung-dung-5" id="iii-xay-dung-ung-dung-5"></a>

#### 3.1 Tạo models <a href="#id-31-tao-models-6" id="id-31-tao-models-6"></a>

Thư mục models này sẽ chứa các models để query DB Models products: File `models/products.js`:

```
module.exports = function(sequelize, DataTypes) {
	return sequelize.define('products', {
		id: {
			type: DataTypes.INTEGER(11).UNSIGNED,
			allowNull: false,
			primaryKey: true,
			autoIncrement: true
		},
		name: {
			type: DataTypes.STRING(256),
			allowNull: false
		},
		color: {
			type: DataTypes.STRING(256),
			allowNull: false
		},
		price: {
			type: DataTypes.STRING(256),
			allowNull: false
		}
	}, {
		tableName: 'products',
		timestamps: false
	});
};
```

#### 3.2 Tạo GraphQL <a href="#id-32-tao-graphql-7" id="id-32-tao-graphql-7"></a>

Với GraphQL, để tạo được ứng dụng CRUD, chúng ta cần phải tạo 2 mục: Query (dùng để get data) và Mutation (dùng để update, create, delete data).

Tại thư mục root của dự án, tạo file `GraphQL/products.js`:

```
const { gql } = require('apollo-server-express');
const db = require('./../database');

const typeDefs = gql`
  type Query {
    products: [Product]
    product(id: ID!): Product
  }
  type Product {
    id: ID!
    name: String!
    color: String!
    price: String!
  }

  type Mutation {
    updateProduct(
      id: Int, 
      name: String, 
      color: String, 
      price: String
    ): String
    createProduct(
      name: String, 
      color: String, 
      price: String
    ): String
    deletProduct(id: Int): Boolean
  }
`

const resolvers = {
  Query: {
    products: async () => db.products.findAll(),
    product: async (obj, args, context, info) =>
      db.products.findByPk(args.id),
  },
  Mutation: {
    createProduct: async (root,args,context,info) => {
      product = await db.products.create({
        name: args.name,
        color: args.color,
        price: args.price,
      });
      return product.id;
    },
    updateProduct: async (root,args,context,info) => {
      if (!args.id) return;
      product = await db.products.update({
        name: args.name,
        color: args.color,
        price: args.price,
      }, {
        where: { id: args.id}
      });
      return 'Update Success!';
    },
    deleteProduct: async (root,args,context,info) => {
      if (!args.id) return;
      product = await db.products.destroy({where: {
        id: args.id
      }})
      return 'Delete success!';
    }
  }
}

module.exports = { typeDefs, resolvers }
```

Trong file products.js ở trên, có 2 mục mình cần quan tâm đó là:

1. `typeDefs`: Dùng để khai báo các thuộc tính dùng để query
2. `resolvers`: Dùng để khai báo Query, Mutation thao tác với cơ sở dữ liệu.

Sau đó export 2 biến `typeDefs`, `resolvers` bằng dòng để file khác sử dụng:

```
module.exports = { typeDefs, resolvers }
```

Code của GraphQL cũng khá dễ hiểu, các bạn có thể đọc và hiểu luôn. Ngoài ra nên tham khảo cách query DB với `sequelize` tại <https://sequelize.org/master/manual/getting-started.html>

#### 3.3 Tạo server run APP <a href="#id-33-tao-server-run-app-8" id="id-33-tao-server-run-app-8"></a>

Tiếp đến, mình sẽ tạo 1 file app.js để run tạo server với nội dung:

```
//app.js
const express = require('express');
require('dotenv').config({path:'.env'});
const { ApolloServer } = require('apollo-server-express');

//Create server with ApolloServer
const server = new ApolloServer({
      modules: [
            require('./GraphQL/products'),
      ]
});

const app = express();
server.applyMiddleware({ app });

//Handle URL not found
app.use(function(req, res) {
      res.status(404).send({url: req.originalUrl + ' not found'})
})

//Start APP on Port 4000
app.listen({ port: 4000 }, () => console.log(`🚀 Server ready at http://localhost:4000/graphql`));
```

Trong file này, chỉ đơn giản mình dùng các thư viện `express`, `dotenv`, `apollo-server-express` để xử lý tạo và quản lý server.

#### 3.4 Review cấu trúc thư mục <a href="#id-34-review-cau-truc-thu-muc-9" id="id-34-review-cau-truc-thu-muc-9"></a>

Sau 1 hồi thì mình đã có được 1 thư mục như này:

![](https://images.viblo.asia/56342c63-c127-4c1a-a2ca-7beae62163d7.png)

Okey, Giờ đến phần testing.

### IV. Testing API <a href="#iv-testing-api-10" id="iv-testing-api-10"></a>

#### 4.1. Get All Products <a href="#id-41-get-all-products-11" id="id-41-get-all-products-11"></a>

Request:

```
{
  products {
    id,
    name,
    color,
    price
  }
}
```

Response:

![](https://images.viblo.asia/f6fb432b-f0f8-447e-8a75-e135a7d1b561.png)

#### 4.2. Get Product Detail <a href="#id-42-get-product-detail-12" id="id-42-get-product-detail-12"></a>

Request:

```
{
  product(id: 2){
    name,
    color,
    price
  }
}
```

Response:

![](https://images.viblo.asia/47f448a5-9df7-4eeb-ac2c-1f8c39e5bfc7.png)

#### 4.3. Create Product <a href="#id-43-create-product-13" id="id-43-create-product-13"></a>

Request

```
mutation {
 createProduct(
   name: "iPhone 11",
   color: "Black",
   price: "14000000",
 )
}
```

Response:

![](https://images.viblo.asia/112ab9d3-642e-4965-a763-ecc72e605d02.png)

#### 4.4. Update Product <a href="#id-44-update-product-14" id="id-44-update-product-14"></a>

Request

```
mutation {
 updateProduct(
   id: 1,
   name: "iPhone X",
   color: "White",
   price: "20000000",
 )
}
```

Response:<br>

![](https://images.viblo.asia/4526f9ca-d455-421e-abd1-93834c7fe85b.png)

#### 4.5. Delete Product <a href="#id-45-delete-product-15" id="id-45-delete-product-15"></a>

```
 mutation {
 deleteProduct(
   id: 1
 )
}
```

Response:

![](https://images.viblo.asia/a2a7dcb9-8443-441e-af6a-94208d6fe3e8.png)

### V. Tài liệu tham khảo <a href="#v-tai-lieu-tham-khao-16" id="v-tai-lieu-tham-khao-16"></a>

1. <https://graphql.org/>
2. <https://sequelize.org/master/manual/getters-setters-virtuals.html#combining-getters-and-setters>
3. <https://graphql.org/graphql-js/mutations-and-input-types/>
4. <https://blog.logrocket.com/from-rest-to-graphql/>
5. <https://www.digitalocean.com/community/tutorials/how-to-set-up-a-graphql-server-in-node-js-with-apollo-server-and-sequelize>

### VI. Kết luận <a href="#vi-ket-luan-17" id="vi-ket-luan-17"></a>

Như vậy mình đã hướng dẫn và demo cho các bạn cách tạo Restfull API - CRUD với `GraphQL`, hy vọng sau bài này các bạn cũng có thể tự tạo được project cho riêng mình sử dụng `GraphQL`.

Hẹn gặp mọi người tại bài viết sau&#x20;
