# Hello World! - Làm quen với Flask-RESTful

{% hint style="info" %}
Bài hướng dẫn này sẽ trình bày những bước lập trình cơ bản trên nền tảng Flask-RESTful để người đọc làm quen và khởi chạy được chương trình "Hello World!". Bài hướng dẫn cũng đính kèm cách khai thác [trợ lý AI](https://flask-restful-docs.dhpit.com/khoi-tao-tro-ly-ai-cung-hoc-lap-trinh) để hỗ trợ lập trình Python và Flask-RESTful.

Khi bạn đọc thấy biểu tượng :robot: nghĩa là đoạn văn bản đó được tạo tự động bởi [trợ lý AI](https://flask-restful-docs.dhpit.com/khoi-tao-tro-ly-ai-cung-hoc-lap-trinh) trên nền tảng [KamiMind](https://about.kamimind.ai/).
{% endhint %}

## Chương trình API đơn giản

Đoạn mã nguồn tạo một ứng dụng RESTful đơn giản với địa chỉ `/` để trả về một đối tượng JSON có với *key* là `hello` và *value* là `world`.

{% code lineNumbers="true" %}

```python
from flask import Flask
from flask_restful import Resource, Api

app = Flask(__name__)
api = Api(app)

class HelloWorld(Resource):
    def get(self):
        return {'hello': 'world'}

api.add_resource(HelloWorld, '/')

if __name__ == '__main__':
    app.run(debug=True)
```

{% endcode %}

{% hint style="info" %}
Bạn đọc có thể trải nghiệm AI tạo sinh văn bản bằng gợi ý (promtp) đơn giản sau:

*Đóng vai là chuyên gia lập trình Flask. Hãy giải thích đoạn mã nguồn sau bằng tiếng Việt. "(chèn đoạn mã nguồn cần giải thích vào cặp ngoặc kép)"*
{% endhint %}

:robot: Giải thích đoạn chương trình:

* Đoạn mã nguồn bắt đầu bằng câu lệnh `from flask import Flask`, để import thư viện Flask.
* Tiếp theo, câu lệnh `from flask_restful import Resource, Api` để import thư viện Flask-RESTful, để tạo ra một ứng dụng web RESTful.
* Câu lệnh `app = Flask(__name__)` tạo một đối tượng ứng dụng Flask, với tên ứng dụng là `__name__`, để chỉ định tên ứng dụng.
* Câu lệnh `api = Api(app)` tạo một đối tượng API, với ứng dụng Flask là `app`.
* Class `HelloWorld` được định nghĩa bên trong `api` và kế thừa từ `Resource`. Phương thức `get` trong class `HelloWorld` sẽ được gọi khi có yêu cầu `GET` đến địa chỉ `/` (địa chỉ Web cấp Root). Phương thức `get` này trả về một đối tượng JSON có *key* là `hello` và *value* là `world`.
* Câu lệnh `api.add_resource(HelloWorld, '/')` thêm class `HelloWorld` vào API, với địa chỉ `/` (địa chỉ Web cấp Root).
* Cuối cùng, câu lệnh `if __name__ == '__main__':` chỉ định rằng đây là một ứng dụng chính, và sẽ được thực thi khi ứng dụng được khởi chạy. Câu lệnh `app.run(debug=True)` chạy ứng dụng với môi trường *debug*. Cần lưu ý rằng tính năng *debug* chỉ nên kích hoạt trong quá trình lập trình, và nên tắt trong quá trình triển khai sử dụng thực tế.

Để thực thi đoạn chương trình trên, trước hết, cần lưu lại thành tập tin và đặt tên **api.py**, sau đó thực thi lệnh `$ python api.py` trong môi trường CMD hoặc Terminal. Lưu ý, cần kích hoạt [môi trường lập trình](https://flask-restful-docs.dhpit.com/cai-dat-moi-truong-lap-trinh) trước khi thực thi lệnh trên.

```
$ python api.py
    * Serving Flask app 'api'
    * Debug mode: on
    * Running on http://127.0.0.1:5000
```

Tương tác với API bằng cách thực thi đoạn mã `$ curl http://127.0.0.1:5000/` trong một cửa sổ CMD hoặc Terminal mới, kết quả trả về là một đối tượng JSON.

<pre><code><strong>$ curl http://127.0.0.1:5000/
</strong>
{
    "Hello": "World"
}
</code></pre>

***

## Định tuyến tài nguyên

Trong đoạn chương trình Python trên, chúng ta thấy rằng API của lớp `HelloWorld` có thể được truy cập từ vị trí `/` (địa chỉ Web cấp Root). Trong phần này, bài hướng dẫn sẽ trình bày cách định tuyến ở mức chi tiết hơn, cũng như cách khai thác các [phương thức HTTP](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods) như GET, PUT.

Đoạn chương trình bên dưới định nghĩa một lớp `SimpleTodo`, bao gồm hai phương thức là `get` và `put`. Trong đó, phương thức get sẽ truy xuất dữ liệu trong danh sách `todos` (cấu trúc dữ liệu `dict`) theo khóa (key) là `todo_id`. Phương thức put sẽ thực hiện lấy dữ liệu từ `request.form['data']` và ghi vào danh sách `todos`.

{% code lineNumbers="true" %}

```python
from flask import Flask, request
from flask_restful import Api, Resource

# Define app configurations
app = Flask(__name__)
api = Api(app)

# Example 2
todos = {}


class SimpleTodo(Resource):
    def get(self, todo_id):
        item = todos.get(todo_id)
        if item is not None:
            return {todo_id: item}
        return {todo_id: 'todo_id is not found!'}

    def put(self, todo_id):
        data = request.form['data']
        if data is not None:
            todos[todo_id] = data
            return {todo_id: todos[todo_id]}
        return {'msg': 'An Unexpected Error Occurred!'}


# Example 1
class HelloWorld(Resource):
    def get(self):
        return {'Hello': 'World'}


# Define URI
api.add_resource(HelloWorld, '/')
api.add_resource(SimpleTodo, '/todo/<string:todo_id>')

if __name__ == '__main__':
    app.run(debug=True)
```

{% endcode %}

{% hint style="info" %}
Để hiểu thêm về cấu trúc dữ liệu `dict` trong Python, bạn đọc có thể sử dụng gợi ý sau để tương tác với [trợ lý AI](https://flask-restful-docs.dhpit.com/khoi-tao-tro-ly-ai-cung-hoc-lap-trinh).

*Đóng vai là một lập trình viên Python. Hãy giải thích và cho ví dụ cấu trúc dữ liệu dict trong Python. Đồng thời liệt kê các cấu trúc dữ liệu tương tự như dict trong Python.*
{% endhint %}

Đoạn chương trình trên có thể được tương tác thông qua các câu lệnh sau trên môi trường CMD hoặc Terminal.

```
$ curl http://127.0.0.1:5000/todo/todo1 -d "data=Remember to do homework" -X PUT
{"todo1": "Remember to do homework"}

$ curl http://127.0.0.1:5000/todo1
{"todo1": "Remember to do homework"}

$ curl http://127.0.0.1:5000/todo/todo2
{"todo2": "todo_id is not found!"}
```

Với mỗi lệnh thực thi ở trên, hãy xem những kết quả trả về tương ứng trong cửa sổ CMD hoặc Terminal khởi chạy chương trình, và để ý phương thức HTTP ở dòng 1 và 2 dưới đây.

```
127.0.0.1 - - [30/Nov/2023 15:08:37] "PUT /todo/todo1 HTTP/1.1" 200 -
127.0.0.1 - - [30/Nov/2023 15:08:41] "GET /todo/todo1 HTTP/1.1" 200 -
127.0.0.1 - - [30/Nov/2023 15:08:45] "GET /todo/todo2 HTTP/1.1" 200 -
```

Flask-RESTful còn cho phép phản hồi (response) một cách tường minh các [mã trạng thái HTTP](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status), chẳng hạn, `200 OK` hay `201 Created`.

```python
class Todo1(Resource):
    def get(self):
        # Default is 200 OK
        return {'Hello': 'world'}

class Todo2(Resource):
    def get(self):
        # Set the response code to 201
        return {'Hello': 'world'}, 201

class Todo3(Resource):
    def get(self):
        # Set the response code to 201 and return custom headers
        return {'Hello': 'world'}, 201, {'test-header': 'enter-text-here'}
```

Để xem được giá trị "custom headers", như trình bày trong lớp `Todo3`, chúng ta có thể sử dụng phần mềm Postman, kết quả được thể hiện như trong hình bên dưới.

<figure><img src="https://2629208750-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FGcGpjujeHlomkGpjFqEJ%2Fuploads%2Fam9SAICsyDLC3TBkkgM2%2FScreenshot%202023-11-30%20at%203.42.40%20PM.png?alt=media&#x26;token=2c60a2ea-b015-4100-8b96-db266b37ca4e" alt=""><figcaption><p>Hình minh họa trong trường hợp sử dụng tính năng "custom headers" trong phản hồi (response) trên phần mềm Postman.</p></figcaption></figure>

***

## Endpoints

Trong bài hướng dẫn này, cũng như trong lĩnh vực lập trình Microservices, thuật ngữ **Endpoint** thường được sử dụng nguyên mẫu tiếng Anh. Để hiểu về khái niệm này, [trợ lý AI](https://flask-restful-docs.dhpit.com/khoi-tao-tro-ly-ai-cung-hoc-lap-trinh) sẽ trình bày bằng cả tiếng Việt và tiếng Anh.

{% hint style="info" %}
:robot: Trong kiến trúc Microservices, **Endpoint** là gì?

Trong kiến trúc Microservices, <mark style="background-color:orange;">thuật ngữ "endpoints" thường được hiểu là các đường dẫn hoặc URL cụ thể nằm trong một dịch vụ</mark>, cho phép các dịch vụ khác hoặc các ứng dụng khác truy cập để tương tác với dịch vụ đó. <mark style="background-color:orange;">Các endpoint có vai trò như các điểm đầu vào (entry points) hoặc giao diện (interface)</mark> mà các thành phần hoặc dịch vụ khác có thể sử dụng để thực hiện các hoạt động như lấy dữ liệu, gửi yêu cầu, hoặc thực thi các chức năng cụ thể. <mark style="background-color:orange;">Mỗi endpoint thường biểu diễn một chức năng hoặc tài nguyên nhất định mà dịch vụ cung cấp</mark>, tạo điều kiện cho việc giao tiếp và tương tác giữa các thành phần khác nhau của hệ thống trong một kiến trúc phân tán như microservices được xác định và quản lý một cách rõ ràng.
{% endhint %}

{% hint style="info" %}
:robot: What is **Endpoint** in Microservices?

In microservices, <mark style="background-color:orange;">"endpoints" refer to specific URLs or routes within a service</mark> that other services or clients can access to interact with that service. <mark style="background-color:orange;">These endpoints serve as entry points or interfaces</mark> through which different components or services can communicate and perform actions, such as retrieving data, sending requests, or executing specific functionalities. <mark style="background-color:orange;">Each endpoint typically represents a particular function or resource provided by the service</mark>, allowing for a clear and well-defined way for communication and interaction between different parts of the system in a distributed architecture like microservices.
{% endhint %}

Flask-RESRful cho phép lập trình khai báo nhiều Endpoints cho cùng một tài nguyên (resource). Cụ thể, lập trình viên chỉ cần truyền thêm các URL vào phương thức `add_resource()` của đối tượng `Api`.

```python
api.add_resource(SimpleTodo,
    '/todo/<string:todo_id>',
    '/todo/')
```

Trong đoạn mã trên, được mở rộng từ chương trình minh họa, chúng ta thấy ưu điểm quan trọng của việc khai báo nhiều Endpoint cho cùng một Resource. Cụ thể, trong trường hợp yêu cầu (request) không bao gồm tham số `<string:todo_id>`, thì chương trình sẽ sử dụng Endpoint tiếp theo, `/todo/` trong tình huống này.

## Xử lý tham số đầu vào

Flask-RESTful được bao gồm sẵn cơ chế hỗ trợ lập trình xử lý tham số đầu vào (argument parsing) cho các API Endpoint.

```python
from flask_restful import reqparse

parser = reqparse.RequestParser()
parser.add_argument('rate', type=int, help='Rate to charge for this resource')
args = parser.parse_args()
```

:robot: Đoạn mã `parser.parse_args()`sẽ trả về một đối tượng chứa các đối số được truyền vào trong mỗi `request`. Trong trường hợp này, nó sẽ trả về một đối tượng chứa đối số `rate` được truyền vào trong `request` và có kiểu dữ liệu là số nguyên (`int`).

{% hint style="info" %}
Bạn đọc có thể tương tác với [trợ lý AI](https://flask-restful-docs.dhpit.com/khoi-tao-tro-ly-ai-cung-hoc-lap-trinh) bằng cách sử dụng và cải thiện đoạn gợi ý (prompt) sau:

*Trong vai trò là lập trình viên Flask-RESTful. Hãy cho tôi biết đoạn mã "parser.parse\_args()" có kết quả đầu ra là gì trong đoạn mã nguồn sau.*

*"(chèn đoạn mã nguồn cần giải thích vào cặp ngoặc kép)"*
{% endhint %}

Khi sử dụng module `reqparse` của Flask-RESTful, trình biên dịch sẽ trả về mã lỗi dựa trên bộ mã trạng thái HTTP và được biểu diễn theo định dạng JSON.

```
$ curl -d 'rate=foo' http://127.0.0.1:5000/SimpleTodo
{'status': 400, 'message': 'foo cannot be converted to int'}
```

***

## Định dạng dữ liệu

Bên cạnh hỗ trợ thao tác trên các cấu trúc dữ liệu của Python, Flask-RESTful hỗ trợ thao tác trên kiểu dữ liệu đối tượng, thông qua module `fields` và decorator (tạm dịch: tiền chỉ thị) `marshal_with()`.

{% hint style="info" %}
:robot: Decorator "marshal\_with()" thường được sử dụng trong các framework như Flask-RESTful để định dạng dữ liệu trước khi trả về từ một API endpoint. Decorator này thường được áp dụng cho các hàm để xử lý việc chuyển đổi dữ liệu thành định dạng mong muốn trước khi gửi về cho người dùng hoặc ứng dụng khác.
{% endhint %}

{% code lineNumbers="true" %}

```python
from flask_restful import fields, marshal_with

resource_fields = {
    'task':   fields.String,
    'uri':    fields.Url('todo_ep')
}

class TodoDao(object):
    def __init__(self, todo_id, task):
        self.todo_id = todo_id
        self.task = task

        # This field will not be sent in the response
        self.status = 'active'

class Todo(Resource):
    @marshal_with(resource_fields)
    def get(self, **kwargs):
        return TodoDao(todo_id='my_todo', task='Remember the milk')
```

{% endcode %}

Trong đoạn chương trình trên, decorator `marshal_with()` sẽ áp dụng quá trình biến đổi được mô tả bởi `resource_fields`. Trong đó, chỉ có thuộc tính `task` được trích xuất từ đối tượng ban đầu, còn thuộc tính `todo_id` bị bỏ qua, theo như định nghĩa trong `resource_fields`. Trường `fields.Url` là một trường đặc biệt nhận đầu vào là tên của Endpoint và tạo ra một URL tương ứng trong phản hồi.

{% hint style="info" %}
:robot: Trong lĩnh vực lập trình, DAO (Data Access Object) là một mô hình thiết kế được sử dụng để tách biệt việc truy cập vào dữ liệu từ logic của ứng dụng. Đây là một thành phần hoặc lớp có nhiệm vụ cung cấp các phương thức để thực hiện các hoạt động truy vấn và cập nhật dữ liệu trong cơ sở dữ liệu. DAO giúp tăng tính mô đun, dễ bảo trì và mở rộng của ứng dụng bằng cách cung cấp một cách tiếp cận logic và cơ sở dữ liệu một cách riêng biệt và linh hoạt.
{% endhint %}

***

## Chương trình "Hello World!"

Trong phần này, bài hướng dẫn sẽ giới thiệu một chương trình hoàn chỉnh được lập trình trên Flask-RESTful. Đoạn chương trình được lưu trong tập tin có tên **api.py**.

{% code lineNumbers="true" %}

```python
from flask import Flask
from flask_restful import reqparse, abort, Api, Resource

app = Flask(__name__)
api = Api(app)

TODOS = {
    'todo1': {'task': 'Getting started with Flask-RESTful'},
    'todo2': {'task': 'Program an use-case'},
    'todo3': {'task': 'Improve the program'},
}


def abort_if_todo_doesnt_exist(todo_id):
    if todo_id not in TODOS:
        abort(404, message="Todo {} doesn't exist".format(todo_id))

parser = reqparse.RequestParser()
parser.add_argument('task')


# Todo
# shows a single todo item and lets you delete a todo item
class Todo(Resource):
    def get(self, todo_id):
        abort_if_todo_doesnt_exist(todo_id)
        return TODOS[todo_id]

    def delete(self, todo_id):
        abort_if_todo_doesnt_exist(todo_id)
        del TODOS[todo_id]
        return '', 204

    def put(self, todo_id):
        args = parser.parse_args()
        task = {'task': args['task']}
        TODOS[todo_id] = task
        return task, 201


# TodoList
# shows a list of all todos, and lets you POST to add new tasks
class TodoList(Resource):
    def get(self):
        return TODOS

    def post(self):
        args = parser.parse_args()
        todo_id = int(max(TODOS.keys()).lstrip('todo')) + 1
        todo_id = 'todo%i' % todo_id
        TODOS[todo_id] = {'task': args['task']}
        return TODOS[todo_id], 201


## Setup the Api resource routing here
api.add_resource(TodoList, '/todos')
api.add_resource(Todo, '/todos/<todo_id>')


if __name__ == '__main__':
    app.run(debug=True)
```

{% endcode %}

:robot: Chương trình trên sử dụng Flask-RESTful để tạo một API cho việc quản lý các công việc (todos). Đây là một ví dụ cơ bản về cách tạo các endpoint để thực hiện các thao tác CRUD (Create, Read, Update, Delete) trên danh sách các công việc.

* Đầu tiên, `import` các module cần thiết từ Flask và Flask-RESTful.
* Tiếp theo, khởi tạo một ứng dụng Flask và một API từ Flask-RESTful.
* Tạo ra một danh sách TODOS chứa các công việc có sẵn, mỗi công việc có một ID duy nhất.

:robot: Tiếp theo, chương trình định nghĩa hai lớp là `Todo` và `TodoList` kế thừa từ lớp `Resource` của Flask-RESTful:

1. Lớp `Todo`:
   * `get`: Trả về thông tin của một công việc cụ thể dựa trên `todo_id`.
   * `delete`: Xoá một công việc dựa trên `todo_id`.
   * `put`: Cập nhật hoặc tạo mới một công việc dựa trên `todo_id`.
2. `TodoList` class:
   * `get`: Trả về danh sách các công việc hiện có.
   * `post`: Tạo mới một công việc và thêm vào danh sách.

:robot: Sau cùng, thiết lập các route (định tuyến) cho hai lớp `TodoList` và `Todo` thông qua `api.add_resource()` để gán các đường dẫn URL cho từng tài nguyên (resource) tương ứng.

Để tương tác với đoạn chương trình trên, trước hết khởi chạy chương trình bằng lệnh `$ python api.py`, sau đó, hãy thực thi các lệnh sau cho từng trường hợp:

1. GET danh sách `TODOS`

```
$ curl http://localhost:5000/todos
```

2. GET một công việc (task)

```
$ curl http://localhost:5000/todos/todo3
```

3. DELETE một công việc (task)

```
$ curl http://localhost:5000/todos/todo2 -X DELETE -v
```

4. Thêm một công việc (task) mới

```
$ curl http://localhost:5000/todos -d "task=do something" -X POST -v
```

5. Cập nhật nội dung một công việc (task)

```
$ curl http://localhost:5000/todos/todo3 -d "task=something different" -X PUT -v
```

***

## Tổng kết

Trong bài hướng dẫn này, chúng ta đã lần lượt trình bày qua những kiến thức chính và cơ bản của Flask-RESTful, gồm:

* Định tuyến tài nguyên
* Endpoints
* Xử lý tham số đầu vào (Argument parsing)
* Định dạng dữ liệu

Sau cùng, chúng ta cùng thực hành một tình huống ứng dụng - Lập trình các API cho ứng dụng quản lý công việc - để thao tác và hiểu rõ hơn cách hoạt động của Flask-RESTful, cũng như hoàn tất một chương trình "Hello World!".
