ํ’€์Šคํƒ ์›น๐ŸŒ ๊ฐœ๋ฐœ์ž ์ง€๋ง์ƒ ๐Ÿง‘๐Ÿฝโ€๐Ÿ’ป
โž• ์ธ๊ณต์ง€๋Šฅ ๊ด€์‹ฌ ๐Ÿค–


Categories


Recent views

  • 1
  • 2
  • 3
  • 4
  • 5

GraphQL with flask

https://dev.to/mesadhan/python-flask-graphql-with-graphene-nla (Md. Sadhan Sarker)์˜ ๊ธ€์„ ๋ฒˆ์—ญ, ์ •๋ฆฌํ•œ ๊ธ€์ž…๋‹ˆ๋‹ค.

  1. GraphQL๊ณผ REST์˜ ์ฐจ์ด ์˜ˆ์‹œ
    • Python Graphene์„ ์ด์šฉํ•œ GraphQL ์„œ๋ฒ„ ๊ตฌํ˜„

      GraphQL with flask

      GraphQL์€ API๋ฅผ ์œ„ํ•œ query language์ด๋‹ค. REST API์— ๋น„ํ•ด ์—ฌ๋Ÿฌ ์žฅ์ ์ด ์žˆ์œผ๋ฉฐ ํŠนํžˆ data fetching ๋ถ€๋ถ„์ด ํšจ์œจ์ ์ด๋‹ค. ๊ทœ๋ชจ๊ฐ€ ํฐ API์ผ์ˆ˜๋ก ๋”์šฑ ํšจ๊ณผ์ ์ด๊ณ  ๊ฐ•๋ ฅํ•˜๋ฉฐ, facebook์—์„œ open source๋กœ ๋ฐฐํฌํ•˜์—ฌ ์ปค๋‹ค๋ž€ ์ปค๋ฎค๋‹ˆํ‹ฐ๋ฅผ ์ด๋ฃจ์—ˆ๋‹ค.

      ์š”์ฆ˜์€ ์„ ์–ธํ˜• ํ”„๋กœ๊ทธ๋ž˜๋ฐ์ด ์ ์  ์ธ๊ธฐ๋ฅผ ์–ป๊ณ  ์žˆ๋Š”๋ฐ, GraphQL ๋˜ํ•œ, ์—ฌ๋Ÿฌ API ํ˜ธ์ถœ์„ ๋ถˆ๋Ÿฌ์™€์•ผํ•˜๋Š” REST API์™€๋Š” ๋‹ฌ๋ฆฌ ์„ ์–ธํ˜• ๋ฐ์ดํ„ฐ fetching์ด์šฉํ•œ๋‹ค. GraphQL ์„œ๋ฒ„๋Š” ์˜ค์ง ํ•˜๋‚˜์˜ endpoint(root URL ๋’ค์— ๋ถ™๋Š” ์ถ”๊ฐ€ ์ฃผ์†Œ)์™€ ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์š”์ฒญํ•œ ์—ฌ๋Ÿฌ response๋กœ ์ด๋ฃจ์–ด์กŒ๋‹ค. ์•ž์œผ๋กœ ์˜ˆ์‹œ๋ฅผ ์‚ดํŽด๋ณผ ๊ฒƒ์ด๋‹ค.

      GraphQL vs REST

      • GraphQL์€ ์ฟผ๋ฆฌ๋กœ ์šฐ๋ฆฌ๊ฐ€ ํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ๋งŒ ๋ช…์‹œํ•˜๊ฒŒ ํ•ด์ฃผ๋ฉฐ, ํ•œ๋ฒˆ์˜ ์š”์ฒญ์œผ๋กœ ์ •ํ™•ํžˆ ๊ทธ ๋ฐ์ดํ„ฐ๋งŒ ์‘๋‹ต์— ํฌํ•จ์‹œ์ผœ์ค„ ๊ฒƒ์ด๋‹ค. ์ด์— ๋ฐ˜ํ•ด REST API๋Š” ์—ฌ๋Ÿฌ ์‘๋‹ต๊ณผ ํ˜ธ์ถœ์„ ์š”๊ตฌํ•œ๋‹ค.

      GraphQL๊ณผ REST์˜ ์ฐจ์ด ์˜ˆ์‹œ

      • API์—์„œ data๋ฅผ ๊ฐ€์ ธ์˜ฌ ๋•Œ, ํฐ ์ฐจ์ด๋ฅผ ๋ณด์—ฌ์ฃผ๋Š”๋ฐ. ๋ธ”๋กœ๊ทธ์—์„œ ์–ด๋–ค ์œ ์ €์˜ ์ž‘์„ฑ๊ธ€๋“ค์˜ ์ œ๋ชฉ์ด ํ•„์š”ํ•  ๊ฒฝ์šฐ, ๋˜ํ•œ ๊ฐ™์€ ํŽ˜์ด์ง€์—์„œ ๊ทธ ์œ ์ €์˜ ์ตœ์‹  ํŒ”๋กœ์›Œ๋“ค 3๋ช…์˜ ์ด๋ฆ„์„ ๊ฐ€์ ธ์˜ค๋ ค ํ•  ๋•Œ, GraphQL๊ณผ REST API๋Š” ์–ด๋–ค ์ฐจ์ด๋ฅผ ๋ณด์ผ๊นŒ?

        REST API์˜ ๊ฒฝ์šฐ

        • REST API์—์„œ๋Š” ์—ฌ๋Ÿฌ endpoint์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜จ๋‹ค.

          • ์˜ˆ๋ฅผ ๋“ค์–ด, /user/<id> endpoint์—์„œ ๋จผ์ € ์œ ์ € ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๋‚Ÿ
          • ๊ทธ ํ›„, /user/<id>/posts endpoint์—์„œ ํ•ด๋‹น ์œ ์ €์˜ ๋ชจ๋“  ๊ฒŒ์‹œ๊ธ€๋“ค์„ ๊ฐ€์ ธ์˜จ๋‹ค
          • ๋งˆ์ง€๋ง‰์œผ๋กœ, /user/<id>/followers์—์„œ ํ•ด๋‹น ์œ ์ €์˜ ๋ชจ๋“  ํŒ”๋กœ์›Œ๋“ค์„ ๊ฐ€์ ธ์˜จ๋‹ค
        • โ€‹ - ๊ทธ๋ฆผ ์ถœ์ฒ˜ : source :howtographql.com

          • REST API๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด 3๋ฒˆ์˜ ๊ฐ์ž ๋‹ค๋ฅธ ์ฃผ์†Œ๋กœ 3๋ฒˆ์˜ ์š”์ฒญ์„ ๋ณด๋‚ด์•ผ ํ•˜๋ฉฐ, ์“ธ๋ฐ์—†๋Š” ์ถ”๊ฐ€ ๋ฐ์ดํ„ฐ๋“ค์„ ์ถ”๊ฐ€๋กœ ๊ฐ€์ ธ์˜จ๋‹ค๋Š” ๊ฒƒ์„ ์•Œ ์ˆ˜ ์žˆ๋‹ค.
      • ์•„๋‹ˆ๋ฉด /users/posts/follwers/<id> ๋ผ๋Š” ์ƒˆ๋กœ์šด endpoint๋ฅผ ๋งŒ๋“ค๊ณ , ์šฐ๋ฆฌ๊ฐ€ ์›ํ•˜๋Š” ๋ฐ์ดํ„ฐ๋งŒ ๊ฐ€์ ธ์˜ค๊ฒŒ ๋งŒ๋“ค์–ด๋„ ๋œ๋‹ค. ํ•˜์ง€๋งŒ ๊ทธ๋ ‡๊ฒŒํ•˜๋ฉด ๋”์งํ•œ ์ƒ์‚ฐ์„ฑ๊ณผ ์žฌ์‚ฌ์šฉ์„ฑ์„ ๊ฐ€์ ธ์˜ฌ ๊ฒƒ์ด๋‹ค. ์ถ”๊ฐ€๋กœ ๋‚ ์งœ์— ๋”ฐ๋ผ ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์˜ค๋ ค๋ฉด /users/posts/follwers/<id>/<date>๋ผ๋Š” endpoint๋ฅผ ๋งŒ๋“ค์–ด์•ผํ•œ๋‹ค.

      GraphQL์˜ ๊ฒฝ์šฐ

      • GraphQL์€ ๋‹จ ํ•˜๋‚˜์˜ query๋ฅผ ์„œ๋ฒ„์— ๋ณด๋‚ธ๋‹ค. ์„œ๋ฒ„๋Š” JSON ํ˜•์‹์˜ respond๋ฅผ ๋Œ๋ ค์ค€๋‹ค.

      • ๊ทธ๋ฆผ ์ถœ์ฒ˜ : source :howtographql.com
      • GraphQL์—์„œ๋Š” ํด๋ผ์ด์–ธํŠธ ์„œ๋ฒ„๊ฐ€ ํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ๋งŒ ์ฟผ๋ฆฌ์— ํฌํ•จํ•  ์ˆ˜ ์žˆ๋‹ค. ๋ฐฑ์—”๋“œ ์„œ๋ฒ„์˜ ์‘๋‹ต ๋˜ํ•œ ์ •ํ™•ํžˆ ์ฟผ๋ฆฌ์— ์ •์˜ํ•œ ๊ตฌ์กฐ๋ฅผ ๋”ฐ๋ฅธ๋‹ค

      ๋ฉ‹์ง€์ง€ ์•Š๋Š”๊ฐ€? ์ด๋ก ์€ ์ถฉ๋ถ„ํ•˜๋‹ˆ ์ด์ œ Python Graphne์„ ์ด์šฉํ•ด์„œ GraphQL ์„œ๋ฒ„๋ฅผ ๋งŒ๋“ค์–ด๋ณด์ž.

      Python Graphene์„ ์ด์šฉํ•œ GraphQL ์„œ๋ฒ„ ๊ตฌํ˜„

      • Graphene์€ python์œผ๋‹ˆ GraphQL ํด๋ผ์ด์–ธํŠธ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์ด๋‹ค. ์ด๋ฅผ ์ด์šฉํ•ด ์šฐ๋ฆฌ๋งŒ์˜ GraphQL ์„œ๋ฒ„๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋‹ค.
        Setting up your poject
      • ๋จผ์ € ํ”„๋กœ์ ํŠธ ๊ฒฝ๋กœ์™€ ํด๋”๋ฅผ ์ƒ์„ฑํ•œ๋‹ค.
        ```bash
        $ mkdir graphql-flask
        $ cd graphql-flask
      - ๊ฐ€์ƒํ™˜๊ฒฝ์„ค์ •์„ ํ†ตํ•ด global package๋“ค๊ณผ์˜ ์ถฉ๋Œ์„ ํ”ผํ•˜๊ณ , ๊ฐ ํ”„๋กœ์ ํŠธ ๋ณ„๋กœ ํŒจํ‚ค์ง€ ๋ฒ„์ „ ๊ด€๋ฆฌ๋ฅผ ์‰ฝ๊ฒŒ ํ•  ์ˆ˜ ์žˆ๋‹ค.
      ```bash
      $ pip install virtualenv
      $ virtualenv venv
      $ source venv/bin/activate
      
      ์ฐธ๊ณ ๋กœ 
      $ deactivate
      ๋กœ ๊ฐ€์ƒ ํ™˜๊ฒฝ์—์„œ ๋‚˜๊ฐˆ ์ˆ˜ ์žˆ๋‹ค.
      
      
      • ํ•„์š”ํ•œ depndency๋ฅผ ๋งž์ถ”๊ธฐ ์œ„ํ•ด ์•„๋ž˜์™€ ๊ฐ™์ด ํ„ฐ๋ฏธ๋„์— ์ž…๋ ฅํ•˜๋ผ
      $ pip install flask flask-graphql flask-migrate flask-sqlalchemy graphene graphene-sqlalchemy
      
      
      • ์ดํ›„ ์šฐ๋ฆฌ์˜ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ์‹คํ–‰ํ•ด์•ผ ํ•œ๋‹ค. ์•„๋ž˜ seed.py๋ฅผ ๋งŒ๋“ค์–ด ๋ณด์ž.
      from app import db, User, Post
      
      db.create_all()     # create tables from models
      
      user1 = User(
          name=&#34;Sadhan Sarker&#34;,
          email=&#39;cse.sadhan@gmail.com&#39;
      )
      
      post1 = Post()
      post1.title = &#34;Blog Post Title 1&#34;
      post1.body = &#34;This is the first blog post 1&#34;
      post1.author = user1
      
      db.session.add(post1)
      db.session.add(user1)
      db.session.commit()
      
      print(User.query.all())
      print(Post.query.all())
      
      
      • ์œ„ ์Šคํฌ๋ฆฝํŠธ๋ฅผ ์‹คํ–‰ํ•˜๋ ค๋ฉด ์•„๋ž˜์™€ ๊ฐ™์ด ์ž…๋ ฅํ•˜๋ผ
        ```bash
        python seed.py
      - ์ข‹๋‹ค! ๊ฑฐ์˜ ๋‹ค๋ฌ๋‹ค! ๋งˆ์ง€๋ง‰์œผ๋กœ app.py ์Šคํฌ๋ฆฝํŠธ๋ฅผ ๋งŒ๋“ค๊ณ  ํ”„๋กœ์ ํŠธ์— ์ถ”๊ฐ€ํ•˜์ž
      ```python
      import os
      
      import graphene
      from flask import Flask
      from flask_graphql import GraphQLView
      from flask_sqlalchemy import SQLAlchemy
      from graphene_sqlalchemy import SQLAlchemyObjectType, SQLAlchemyConnectionField
      
      app = Flask(__name__)
      
      basedir = os.path.abspath(os.path.dirname(__file__))
      
      # Database Configs [Check it base on other Database Configuration]
      app.config[&#39;SQLALCHEMY_DATABASE_URI&#39;] = &#39;sqlite:///&#39; + os.path.join(basedir, &#39;database.sqlite&#39;)
      app.config[&#39;SQLALCHEMY_COMMIT_ON_TEARDOWN&#39;] = True
      app.config[&#39;SQLALCHEMY_TRACK_MODIFICATIONS&#39;] = True
      
      # Initialize Database
      db = SQLAlchemy(app)
      
      
      # ------------------  Database Models ------------------
      
      class User(db.Model):
          __tablename__ = &#39;users&#39;
          id = db.Column(db.Integer, primary_key=True)
          name = db.Column(db.String(256))
          email = db.Column(db.String(256), index=True, unique=True)  # index =&#38;#62; should not be duplicate
          posts = db.relationship(&#39;Post&#39;, backref=&#39;author&#39;)
      
          def __repr__(self):
              return &#39;&#38;#60;User &#37;r&#38;#62;&#39; &#37; self.email
      
      
      class Post(db.Model):
          __tablename__ = &#39;posts&#39;
          id = db.Column(db.Integer, primary_key=True)
          title = db.Column(db.String(256))
          body = db.Column(db.Text)
          author_id = db.Column(db.Integer, db.ForeignKey(&#39;users.id&#39;))
      
          def __repr__(self):
              return &#39;&#38;#60;Post &#37;r&#38;#62;&#39; &#37; self.title
      
      
      # ------------------ Graphql Schemas ------------------
      
      
      # Objects Schema
      class PostObject(SQLAlchemyObjectType):
          class Meta:
              model = Post
              interfaces = (graphene.relay.Node,)
      
      
      class UserObject(SQLAlchemyObjectType):
          class Meta:
              model = User
              interfaces = (graphene.relay.Node,)
      
      
      class Query(graphene.ObjectType):
          node = graphene.relay.Node.Field()
          all_posts = SQLAlchemyConnectionField(PostObject)
          all_users = SQLAlchemyConnectionField(UserObject)
      
      
      # noinspection PyTypeChecker
      schema_query = graphene.Schema(query=Query)
      
      
      # Mutation Objects Schema
      class CreatePost(graphene.Mutation):
          class Arguments:
              title = graphene.String(required=True)
              body = graphene.String(required=True)
              email = graphene.String(required=True)
      
          post = graphene.Field(lambda: PostObject)
      
          def mutate(self, info, title, body, email):
              user = User.query.filter_by(email=email).first()
              post = Post(title=title, body=body)
              if user is not None:
                  post.author = user
              db.session.add(post)
              db.session.commit()
              return CreatePost(post=post)
      
      
      class Mutation(graphene.ObjectType):
          save_post = CreatePost.Field()
      
      
      # noinspection PyTypeChecker
      schema_mutation = graphene.Schema(query=Query, mutation=Mutation)
      
      
      # Flask Rest &#38; Graphql Routes
      @app.route(&#39;/&#39;)
      def hello_world():
          return &#39;Hello From Graphql Tutorial!&#39;
      
      
      # /graphql-query
      app.add_url_rule(&#39;/graphql-query&#39;, view_func=GraphQLView.as_view(
          &#39;graphql-query&#39;,
          schema=schema_query, graphiql=True
      ))
      
      # /graphql-mutation
      app.add_url_rule(&#39;/graphql-mutation&#39;, view_func=GraphQLView.as_view(
          &#39;graphql-mutation&#39;,
          schema=schema_mutation, graphiql=True
      ))
      
      if __name__ == &#39;__main__&#39;:
          app.run()
      
      
      • ์ด์ œ ์šฐ๋ฆฌ๋Š” graphene์œผ๋กœ graphql์„ ๋งŒ๋“ค์—ˆ๋‹ค. ์‹คํ–‰ํ•ด๋ณด์ž.
        ```bash
        $ python app.py
      ## GraphQL API ํ…Œ์ŠคํŠธ
      - PostMan์ด๋‚˜ cRUL์„ ์ด์šฉํ•ด ์„œ๋ฒ„์™€ ํ†ต์‹ ํ•ด๋ณด์ž.
      - GraphQL์€ ์›์น™์ ์œผ๋กœ POST์™€ GET ์š”์ฒญ๋งŒ ๋ฐ›๋Š”๋‹ค.
      ```postman
      ## 1. Rest API examples
      GET http://127.0.0.1:5000/
      
      ### Graphql query-api example
      POST http://127.0.0.1:5000/graphql-query
      Content-Type: application/graphql
      
      &#123;
        allPosts&#123;
          edges&#123;
            node&#123;
              title
              author&#123;
                email
              &#125;
            &#125;
          &#125;
        &#125;
      &#125;
      
      ### 2. Graphql mutation-api example
      
      POST http://127.0.0.1:5000/graphql-mutation
      Content-Type: application/graphql
      
      mutation &#123;
        savePost(email:&#34;cse.sadhan@gmail.com&#34;, title:&#34;Title 2&#34;, body:&#34;Blog post 2&#34;) &#123;
          post&#123;
            title
            body
            author&#123;
              email
            &#125;
          &#125;
        &#125;
      &#125;
      
      ###