How to Build a Flask REST API

Python과 venv가 기본적으로 설치되어 있는 환경에서 작업합니다.

필요한 패키지들은 아래 requirement.txt와 같습니다.

aniso8601==9.0.1
blinker==1.9.0
click==8.1.7
colorama==0.4.6
Flask==3.1.0
Flask-RESTful==0.3.10
Flask-SQLAlchemy==3.1.1
greenlet==3.1.1
itsdangerous==2.2.0
Jinja2==3.1.4
MarkupSafe==3.0.2
pytz==2024.2
six==1.16.0
SQLAlchemy==2.0.36
typing_extensions==4.12.2
Werkzeug==3.1.3

프로젝트폴더에 api.py라는 이름으로 파일을 하나 만들어서 아래와 같이 저장합니다.

from flask import Flask

app = Flask(__name__)

@app.route('/')
def home():
    return '<h1>Flask REST API</h1>'

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

그리고 해당 앱을 실행함으로써 Flask API서버를 실행합니다.

 $ python api.py
 * Serving Flask app 'api'
 * Debug mode: on
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
 * Running on http://127.0.0.1:5000
Press CTRL+C to quit
 * Restarting with stat
 * Debugger is active!
 * Debugger PIN: 211-059-641

브라우저에서 http://127.0.0.1:5000를 열어서 서버가 문제없이 작동하는지 확인합니다.

이제 데이타베이스를 연동해볼게요. api.py에 SQLAlchemy를 사용하겠다고 명시해주세요. 그리고 데이타베이스 위치를 명시해준 뒤에 아래와 같이 User모델을 추가해주세요.

from flask import Flask
from flask_sqlalchemy import SQLAlchemy

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///database.db'
db = SQLAlchemy(app)

class UserModel(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(80), unique=True, nullable=False)
    email = db.Column(db.String(80), unique=True, nullable=False)

    def __repr__(self):
        return f"User(name = {self.name}, email = {self.name})"

...

이 파일에서 정의한 모델을 데이타베이스에 생성할 파일을 하나 만듭니다. create_db.py를 만들어서 아래와 같이 코딩해주세요. api.py에서 app과 db를 가져와서 해당 모델들을 생성해줍니다.

from api import app, db 

with app.app_context():
    db.create_all()

그리고 실행합니다.

python create_db.py

그러면 instance 폴더에 SQLite데이타베이스가 생성된것을 확인하실수 있습니다.

$ ls -al instance
total 16
drwxr-xr-x   3 slim  staff     96 Nov 24 01:51 ./
drwxr-xr-x  17 slim  staff    544 Nov 24 01:49 ../
-rw-r--r--   1 slim  staff  16384 Nov 24 01:51 database.db

이제 User테이블에서 데이타를 가져오는 코드를 추가해볼게요. import 맨 끝에 flask_restful에서 Resource, Api, reqparse를 사용하겠다고 명시합니다. 그리고 GET, POST, DELETE등의 RestfulAPI에 필요한 프로토콜들을 사용하기 위해서 Api()함수를 실행하여 api변수에 담습니다. 그리고 UserModel밑에 Users라는 API클래스를 하나 선언합니다. 그리고 그 안에 get이라는 이름의 함수를 선언하는데요, 맞습니다. RestfulAPI에서 GET으로 해당 route이 호출되었을때 어떤 값을 넘겨줄지를 여기에서 정의하는 거에요. 저는 여기서 일단 UserModel에서 쿼리를 해오는데 일단 다 가져오도록 합니다. 그리고 그 값을 return하면 Flask가 척척 알아서 결과를 출력할거에요. 이제 이 클래스를 호출할 route을 그 밑에 추가합니다. 저는 /users/로 했어요.

...
from flask_restful import Resource, Api

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///database.db'
db = SQLAlchemy(app)
api = Api(app)

class UserModel(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(80), unique=True, nullable=False)
    email = db.Column(db.String(80), unique=True, nullable=False)

    def __repr__(self):
        return f"User(name = {self.name}, email = {self.name})"

class Users(Resource):
    def get(self):
        users = UserModel.query.all()
        return users

api.add_resource(Users, '/users/')
...

그럼 서버를 다시 실행해볼까요?

python api.py

그리고 브라우저에서 http://127.0.0.1:5000/users/를 열어봅니다.

현재 User테이블에 아무것도 안들어가 있으니까 당연히 빈 배열방을 넘겨주겠죠. 그런데 RestAPI결과 값은 보통 JSON형식으로 보여줍니다. 결과를 보여주는 포멧을 한번 바꿔볼게요. JSON형식의 결과를 미리 정의하기 위해서는 flask_restful패키지의 fields가 필요합니다.

from flask_restful import Resource, Api, fields

그리고 UserModel뒤에 아래와 같이 입/출력 데이타의 JSON 포멧을 정의해줄게요.

userFields = {
    'id': fields.Integer,
    'name': fields.String,
    'email': fields.String,
}

그리고 나서 정의한 포멧을 GET함수에 추가해줄건데요. 이때 필요한 패키지가 또 있습니다. marshal_with라고 출력할 포멧을 지정할때 Flask에서 필요한 데코레이터입니다.

from flask_restful import Resource, Api, fields, marshal_with

데코레이터는요, 기존의 Object를 꾸미는 용도로 사용하는 건데요. 보통 Object위에 붙여서 기존의 Object형식은 그대로 사용하면서 해당 Object에 필요한 함수나 변수들을 추가로 넣을 수 있는 기능입니다. Users클래스의 get()함수 바로 위에 @으로 시작되는 코드를 아래와 같이 추가해주세요. marshal_with에게 userFields를 인자로 넘겨주어서, 출력형식으로는 아까 정의해둔 userFields를 사용하겠다고 명시합니다.

class Users(Resource):
    @marshal_with(userFields)
    def get(self):
        users = UserModel.query.all()
        return users

현재 우리가 가지고 있는 데이타가 없기때문에 사용자 정보를 추가하기 위해서는 POST로 데이타를 받아서 처리해주는 부분이 필요합니다. 사용자에게 데이타를 받기 위해서는 받을 인자들을 미리 정의해 놓아야하는데 이때 Flask에서 사용하는 패키지가 바로 reqparse입니다.

from flask_restful import Resource, Api, fields, marshal_with, reqparse

그리고 userFields를 선언하기 전에 아래와 같이 사용자인자를 미리 정의합니다. reqparse패키지의 RequestParser()함수를 사용하여 user_args객체를 선언하고 해당 객체에 add_argument()함수를 통해서 각 인자들의 세부사항을 적어줍니다. name이랑 email 이렇게 두개를 받을 건데요, type은 문자열, 그리고 필수입력사항으로 정의할게요. 이렇게 선언을 하면 HTTP Request가 들어왔을때 함께 들어온 이름, 여기서는 name과 email이 되겠죠. 해당 이름의 인자들을 Request데이타에서 받아서 친절하게 user_args에 저장을 해줍니다.

user_args = reqparse.RequestParser()
user_args.add_argument('name', type=str, required=True, help="Name cannot be blank")
user_args.add_argument('email', type=str, required=True, help="Email cannot be blank")

이제 Users클래스에 POST요청이 들어왔을때 처리해줄 post()함수를 선언합니다. 사용자가 입력할 데이타도 Body에서 JSON으로 전달받을거니까 여기서도 마찬가지로 @marshal_with(userFields)데코레이터를 사용해줍니다. 위에서 받아온 매개변수, user_args를 사용할건데요. reqparse.RequestParser()가 넘겨준 결과값은 Object이기때문에 편리한 사용을 위해서 해당 Object의 pase_args()함수를 호출해서 배열에 담아줄게요. 그리고 해당값으로 UserModel을 하나 생성하고, 그 모델을 db.session에 추가해줌으로써 Insert를 완료하게됩니다. 사용자를 추가한 후에는 사용자목록을 받아와서 보여주도록 할게요. 이때 반환하는 HTTP code는 사용자를 성공적으로 생성했다는 의미로 201을 넘겨줍니다.

    @marshal_with(userFields)
    def post(self):
        args = user_args.parse_args()
        user = UserModel(name=args["name"], email=args["email"])
        db.session.add(user)
        db.session.commit()
        users = UserModel.query.all()
        return users, 201

결과를 확인해보려면, Postman이나 Thunder Client같은 클라이언트 툴이 필요한데요, 저는 Visual Studio Code에서 Thunder Client모듈을 설치해서 테스트를 해보도록 할게요. 프로토콜은 POST로 경로를 적어주고, Body에 저장할 이름과 이메일을 JSON으로 넘겨줍니다.

전송버튼을 누르면 201코드와 함께 위에서 코딩한 대로 사용자목록을 Response로 반환해줍니다.

사용자를 생성할때 필수사항으로 정의한 필드를 빠뜨리면 어떻게 될까요? 이메일을 넣지않고 호출을 해볼게요.

Flask가 친절하게도 400코드를 메세지와 함께 반환해주네요.

이번에는 ID를 넘겨주어 한명의 User만 가져오는 기능을 넣어볼게요. Users클래스 밑에 User라는 리소스를 하나 더 만들거에요. 마찬가지로 결과를 JSON으로 결과를 보여줄거니까 marshal_with데코레이터 추가하고요, 프로토콜은 GET이 될테니까 get()함수에다 정의해줄게요. 이때 get()함수에 self이외에 id를 추가해서 사용자에게서 받은 id를 함수안에서 사용할 수 있게 같이 받습니다. 그리고 넘겨받은 id를 가지고 UserModel의 id에 넣어서 filtering을 합니다. 그럴일은 없겠지만 혹시 결과가 여러개 나오면 처음 나오는것만 하나 보여주도록합니다. 이때, 혹시 검색하는 사용자가 없다면 더이상 진행을 하지않고, abort함수를 사용해서 404를 반환하여 찾는 사람이 없다는 걸 알려줍니다. 리소스가 다 완성이 되면 api.add_resource()를 통해서 새로운 route를 추가하여 /users/경로에 정수형의 id가 붙어서 들어오면 User라는 리소스를 통해서 요청을 처리한다고 명시합니다.

from flask_restful import Resource, Api, fields, marshal_with, reqparse, abort

class User(Resource):
    @marshal_with(userFields)
    def get(self, id):
        user = UserModel.query.filter_by(id=id).first()
        if not user:
            abort(404, "User not found")
        return user

api.add_resource(Users, '/users/')
api.add_resource(User, '/users/<int:id>')

요청은 /users/id로 아래와 같이 하면 됩니다.

결과는 배열없이 단일 객체로 id가 1인 사용자를 반환합니다.

이번에는 기존의 데이타를 수정하는 경로를 PATCH프로토콜로 구현해볼게요. User클래스에 patch함수를 정의하고, JSON으로 데이타를 주고 받을 거니까 marshal_with 데코레이터를 추가합니다. Request URL에서 id를 받아오도록 함수의 인자에 명시하고, Body에 JSON으로 받아온 매개변수를 args에 배열로 저장합니다. id를 가지고 수정할 사용자를 찾아와서 user모델에 저장한뒤 해당 모델의 name과 email을 받아온 데이타로 대체합니다. 그리고나서 commit()를 하면 변경된 모델이 데이타베이스에 적용이됩니다.

    @marshal_with(userFields)
    def patch(self, id):
        args = user_args.parse_args()
        user = UserModel.query.filter_by(id=id).first()
        if not user:
            abort(404, "User not found")
        user.name = args["name"]
        user.email = args["email"]
        db.session.commit()
        return user

PATCH프로토콜을 테스트할때는 경로에 변경할 사용자의 ID를 명시하고 Body에 JSON포멧으로 코드에서 명시한 형식에 따라 수정할 데이타를 넘겨줍니다.

그러면 변경된 결과를 반환하죠.

마지막으로 DELETE프로토콜도 구현을 해볼게요. 마찬가지로 marshal_with데코레이터를 넣고, id를 넘겨받는 delete함수를 선언해서 해당 id로 사용자를 찾습니다. 그리고 db.session.delete()함수를 사용해서 user를 삭제한뒤 commit()하면 처리가 되는데 다 처리가 되면 사용자목록을 반환합니다.

    @marshal_with(userFields)
    def delete(self, id):
        user = UserModel.query.filter_by(id=id).first()
        if not user:
            abort(404, "User not found")
        db.session.delete(user)
        db.session.commit()
        users = UserModel.query.all()
        return users

요청은 Body없이 DELETE프로토콜에 id를 넣은 경로로 호출을 합니다. 삭제를 위해서 제가 미리 id가 2인 사용자를 등록해놨어요. 한번 지워볼게요.

그러면 삭제완료후 남은 사용자의 목록을 보여줍니다.

여기까지 잘 따라와 주셔서 감사합니다.

Resources