Skip to content

Latest commit

 

History

History
1153 lines (946 loc) · 32.6 KB

File metadata and controls

1153 lines (946 loc) · 32.6 KB

GraphQL

Contents

  1. what is GraphQL?
  2. How to use GraphQL
  3. 프로젝트에 적용하기

What is GraphQL?

GraphQL vs REST

우선 REST API와 비교해봅시다

REST API : Stateless 하기 때문에 데이터를 주고받기 위해 Client와 독립적인 API를 사용합니다.

  • REST API 의 한계

    • GET /post 요청 : Fetch Post

      {
          id: '1',
          title: 'First Post',
          content: '...',
          creator: {...}
      }

      만약에 id 값만 필요하고 content는 필요하지 않다면?

    • 그리고 서비서가 계속 커진다면 Endpoint(router) 를 계속해서 만들어야하고 관리가 어려워 질것입니다.

    이 단점들을 해소하기 위해 GraphQL을 Facebook 에서 만들었습니다.!

GraphQL API : GraphQL 또한 Stateless 하지만 데이터를 주고받기 위해 REST API에는 없던 다양한 query를 사용할 수 있습니다. GraphQL은 POST 요청으로 통신하고 body에 query 문을 넣습니다.

따라서 Endpoint를 하나만 사용하고 Client 서버에서 오는 다양한 요청들을 처리할 수 있습니다.

GraphQL

GraphQL Query 구조

{
	query {
		user{
			name
			age
		}
	}
}

Operation Types

  • Query => Retrieve Data ("GET")
  • Mutation => Manipulate Data ("POST", "PUT", "PATCH", "DELETE")
  • Subscription => Set up real-time connection via WebSockets

GraphQL Big Picture

GraphQL_BIG_Picture

How does GraphQL Work?

  • 일반 Node(+Express) Server!
  • One Single Endpoint (typically / graphql)
  • POST 요청을 사용해서 body에 데이터 구조를 넣습니다.
  • Server-Side에서 Resolver가 request body를 분석해서 데이터를 가져오고 준비해서 리턴합니다.

프로젝트에 적용하기

기존 프로젝트에 세팅하기

  1. socket.io 삭제하기
  2. app.js 라우터 삭제하기
  3. router 폴더 삭제하기

GraphQL 적용하기

  1. $ npm i --save graphql express-graphql

  2. graphql/schema.js

    • schema 정의하기

      const { buildSchema } = require('graphql');
      
      module.exports = buildSchema(`
      
          type TestData {
              text: String!
              views: Int!
          }
      
          type RootQuery {
              hello: TestData!
          }    
      
          schema {
              query: RootQuery
          }
      `);
  3. graphql/resolvers.js

    • resolvers 정의하기

      module.exports = {
        hello() {
          return {
            text: 'Hello World!',
            views: 1234
          };
        }
      };
  4. app.js

    • graphql 가져오기

      const graphqlHttp = require('express-graphql');
      
      const graphqlSchema = require('./graphql/schema')
      const graphqlResolver = require('./graphql/resolvers')
      
      app.use(
        '/graphql',
        graphqlHttp({
          schema: graphqlSchema,
          rootValue: graphqlResolver
        })
      );
  5. postman으로 테스트 하기

    • postman request : http://localhost:8080/graphql로 아래 쿼리문 보내기

      {
      	"query": "{ hello { text } }"
      }
    • response

      {
          "data": {
              "hello": {
                  "text": "Hello World!"
              }
          }
      }

프론트페이지 세팅하기

  1. socket.io 삭제하기

schema , resolve 정의하기

  1. graphql/shcmea.js

    • 스키마 구성하기

      const { buildSchema } = require('graphql');
      
      module.exports = buildSchema(`
      
          type Post {
              _id: ID!
              title: String!
              content: String!
              imageUrl: String!
              creator: User!
              createdAt: String!
              uadatedAt: String!
          }
      
          type User{
              _id: ID!
              name: String!
              email: String!
              password: String
              status: String!
              posts: [Post!]!
          }
      
          input UserInputData {
              email: String!
              name: String!
              password: String!
          }
      
          type RootMutation {
              createUser(userInput: UserInputData): User!
          }
      
          schema {
              mutation: RootMutation
          }
      `);
  2. graphql/resolves.js

    • createUser resolve 구성하기

      const bcrypt = require('bcryptjs');
      
      const User = require('../models/user');
      
      module.exports = {
        createUser: async function({ userInput }, req) {
          //const email = args.userInput.email
          const existingUser = await User.findOne({ eamil: userInput.email });
          if (existingUser) {
            const error = new Error('User exists already!');
            throw error;
          }
          const hashedPw = await bcrypt.hash(userInput.password, 12);
          const user = new User({
            email: userInput.email,
            name: userInput.name,
            password: hashedPw
          });
          const createdUser = await user.save();
          return { ...createdUser._doc, _id: createdUser._id.toString() };
        }
      };

브라우저에서 graphql 구성 보기

  1. app.js

    • graphql 정의하는 함수에 graphiql: true 넣어주기

      app.use(
        '/graphql',
        graphqlHttp({
          schema: graphqlSchema,
          rootValue: graphqlResolver,
          graphiql: true
        })
      );
  2. localhost:8080/graphql 로 접속하면 GraphiQL 페이지가 나온다

GraphiQL

사용자 값 검증하기!

  • 기존 REST API에서는 Router에서 직접 사용자 Input을 검증하는 로직을 사용했습니다.
  • GraphQL에서는 Endpoint가 하나 이므로 각 요청을 처리하는 resolve에서 사용자 Input을 검증하는 로직을 사용하도록 하겠습니다.
  1. $ npm i --save validator

    • validator를 설치합니다. 기존 express-validator와 다르게 JavaScript 코드를 사용하여 직접 검증할 수 있습니다.(?)
  2. graphql/resolves.js

    • validator 가져오기

      const vlidator = require('validator')
    • Email 체크, 비밀번호 길이 검증 로직 구현하기

      module.exports = {
        createUser: async function({ userInput }, req) {
          const errors = [];
          if (!validator.isEmail(userInput.email)) {
            errors.push({ message: '이메일이 유효하지 않습니다.' });
          }
          if (
            validator.isEmpty(userInput.password) ||
            !validator.isLength(userInput.password, { min: 5 })
          ) {
            errors.push({ meesage: '비밀번호를 최소 5글자 이상 넣어주세요.' });
          }
          if (errors.length > 0) {
            const error = new Error('Invalid input.');
            error.data = errors;
            error.code = 422;
            throw error;
          }
      
          const existingUser = await User.findOne({ email: userInput.email });
          if (existingUser) {
            const error = new Error('User exists already!');
            throw error;
          }
          const hashedPw = await bcrypt.hash(userInput.password, 12);
          const user = new User({
            email: userInput.email,
            name: userInput.name,
            password: hashedPw
          });
          const createdUser = await user.save();
          return { ...createdUser._doc, _id: createdUser._id.toString() };
        }
      };

GraphQL 오류 customFormatErrorFn 로 커스텀 오류 메세지 만들기

  • 위 과정에서 잘못된 E-mail을 보내주었을때 아래와 같이 단순한 오류 메세지를 보내줬습니다. 보다시피 오류에 대한 정보가 충분하지 않습니다.

    {
      "errors": [
        {
          "message": "Invalid input.",
          "locations": [
            {
              "line": 2,
              "column": 3
            }
          ],
          "path": [
            "createUser"
          ]
        }
      ],
      "data": null
    }
  • 오류를 더 구체화 해서 Client 서버로 보내줍시다.

  1. app.js

    • graphql 함수에 인자로 formatError()메소드를 추가해줍시다.

      app.use(
        '/graphql',
        graphqlHttp({
          schema: graphqlSchema,
          rootValue: graphqlResolver,
          graphiql: true,
          formatError(err) {
            // 타이핑오류라던지 기술적 오류가 없으면 실행
            if (!err.originalError) {
              return err;
            }
            const data = err.originalError.data;
            const message = err.message || 'An error ocuured';
            const code = err.originalError.code || 500;
            return { message: message, status: code, data: data };
          }
        })
      );
  2. graphql/resolves.js

    • Error 객체에 에러 정보 담기

      module.exports = {
        createUser: async function({ userInput }, req) {
          const errors = [];
          if (!vlidator.isEmail(userInput.email)) {
            errors.push({ message: '이메일이 유호하지 않습니다.' });
          }
          if (
            !vlidator.isEmpty(userInput.password) ||
            validator.isLength(userInput.password, { min: 5 })
          ) {
            errors.push({ meesage: '비밀번호를 최소 5글자 이상 넣어주세요.' });
          }
          if (errors.length > 0) {
            // error에 정보 
            const error = new Error('Invalid input.');
            error.data = errors;
            error.code = 422;
            throw error;
          }
            ...

    Front Server 설정하기

    1. App.js

      • signupHandler

        signupHandler = (event, authData) => {
            event.preventDefault();
            this.setState({ authLoading: true });
            //GrapQl Query 설정
            const graphqlQuery = {
              query: `
              mutation {
                createUser(userInput: 
                  {
                    email: "${authData.signupForm.email.value}", 
                    name: "${authData.signupForm.name.value}", 
                    password: "${authData.signupForm.password.value}"}) 
                    {
                     _id
                     email
                    }
              }
              `
            };
        
            fetch('http://localhost:8080/graphql', {
              method: 'POST',
              headers: {
                'Content-Type': 'application/json'
              },
              body: JSON.stringify(graphqlQuery)
            })
              .then(res => {
                return res.json();
              })
              .then(resData => {
                if (resData.errors && resData.errors[0].status === 422) {
                  throw new Error(
                    "Validtaion failed. Make user the email address isn't used yet!"
                  );
                }
                if (resData.errors) {
                  throw new Error('User creation failed!');
                }
                console.log(resData);
                this.setState({ isAuth: false, authLoading: false });
                this.props.history.replace('/');
              })
              .catch(err => {
                console.log(err);
                this.setState({
                  isAuth: false,
                  authLoading: false,
                  error: err
                });
              });
          };
    2. Back Server : app.js

      • 보통 브라우저가 요청을 보내기전에 'OPTIONS'를 서버로 보냅니다. 만약 POST, GET 요청이 아니면 GraphQL에서 자동적으로 다른 요청을 막습니다. 따라서 'OPTIONS' 요청을 허용할 수 있게 해줘야 합니다.

        app.use((req, res, next) => {
          res.setHeader('Access-Control-Allow-Origin', '*');
          res.setHeader(
            'Access-Control-Allow-Headers',
            'GET, POST, PUT, PATCH, DELETE'
          );
          res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
          console.log(req.method);
            // OPTIONS 요청 허용
          if (req.method === 'OPTIONS') {
            return res.sendStatus(200);
          }
          next();
        });

    Login 기능 추가

    • GraphQL에서도 REST API와 마찬가지로 Client를 Stateless 하게 관리합니다. 따라서 토큰을 request에 보내주는 방식을 사용합시다.
    1. graphql/schema.js

      • 로그인 쿼리와 토큰 설정해주기

        		...
        type AuthData {
                token: String!
                userId: String!
            }
        
        
            type RootQuery {
                login(email: String!, password: String!): AuthData!
            }
                ...
    2. graphql/resolvers.js

      • login 메소드 작성하기, jwt 가져오기

        const jwt = require('jsonwebtoken');
        
        module.exports = {
        	...
          login: async function ({ email, password }) {
            const user = await User.findOne({ email: email });
            if (!user) {
              const error = new Error('User not found');
              error.code = 401;
              throw error;
            }
            const isEqual = await bcrypt.compare(password, user.password);
            if (!isEqual) {
              const error = new Error('Password is incorrect.');
              error.code = 401;
              throw error;
            }
            const token = jwt.sign(
              {
                userId: user._id.toString(),
                email: user.email,
              },
              'somesupersupersecretfromminjae',
              { expiresIn: '1h' }
            );
            return { token: token, userId: user._id.toString() };
          },
        };
    3. Client : App.js

      • loginHandler GraphQL에 쿼리 보내기

      loginHandler = (event, authData) => { event.preventDefault(); const graphqlQuery = { query: { login(email: "${authData.email}", password: "${authData.password}) { token userId } }, }; this.setState({ authLoading: true }); fetch('http://localhost:8080/graphql', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(graphqlQuery), }) .then((res) => { return res.json(); }) .then((resData) => { if (resData.errors && resData.errors[0].status === 422) { throw new Error( "Validtaion failed. Make user the email address isn't used yet!" ); } if (resData.errors) { throw new Error('User login failed!'); } // REST API 객체 형태 > GraphQL 객체 형태 console.log(resData); this.setState({ isAuth: true, token: resData.data.login.token, authLoading: false, userId: resData.data.login.userId, }); localStorage.setItem('token', resData.data.login.token); localStorage.setItem('userId', resData.data.login.userId); const remainingMilliseconds = 60 * 60 * 1000; const expiryDate = new Date( new Date().getTime() + remainingMilliseconds ); localStorage.setItem('expiryDate', expiryDate.toISOString()); this.setAutoLogout(remainingMilliseconds); }) ... };

      
      

    BackEnd에 post 생성하는 기능 추가하기

    1. graphql/schema.js

      • 스키마 작성하기

        const { buildSchema } = require('graphql');
        
        module.exports = buildSchema(`
           ...
        input PostInputData {
                title: String!
                content: String!
                imageUrl: String!
            }
        
            type RootMutation {
                createUser(userInput: UserInputData): User!
                createPost(postInput: PostInputData): Post!
            }
        	...
        `
        );
    2. graphql/resolves.js

      • createPost resolve 만들기, mongoose Post 모델 가져오기

        const Post = require('../models/post');
        
        module.exports = {
          			
            	...
            
          createPost: async function ({ postInput }, req) {
            // 1. userInput 검사하기
            const errors = [];
            if (
              validator.isEmpty(postInput.title) ||
              !validator.isLength(postInput.title, { min: 3 })
            ) {
              errors.push({ message: 'Title is invalid.' });
            }
            if (
              validator.isEmpty(postInput.content) ||
              !validator.isLength(postInput.content, { min: 3 })
            ) {
              errors.push({ message: 'content is invalid.' });
            }
            if (errors.length > 0) {
              const error = new Error('Invalid Input');
              error.data = errors;
              error.code = 422;
              throw error;
            }
            // 2. 로직수행 (토큰 검사)
            // 3. mongoDB에 저장하기
            const post = new Post({
              title: postInput.title,
              imageUrl: postInput.imageUrl,
              content: postInput.content,
            });
        
            const createdPost = await post.save();
            console.log(createdPost);
            return {
              ...createdPost._doc,
              _id: createdPost._id.toString(),
              createdAt: createdPost.createdAt.toISOString(),
              updatedAt: createdPost.updatedAt.toISOString(),
            };
          },
        };
    3. middleware/auth.js

      • 토큰 검증 로직 GraphQL 구조에서는 REST API와 비슷하지만 약간 다르게 토큰이 검증되면 error를 바로 throw하지 않고 header에 true 또는 false상태를 넣어 줍니다.

        const jwt = require('jsonwebtoken');
        
        module.exports = (req, res, next) => {
          const authHeader = req.get('Authorization');
          if (!authHeader) {
            req.isAuth = false;
            return next();
          }
          const token = authHeader.split(' ')[1];
          let decodedToken;
          try {
            // 2번째 인자는 이전에 설정했던 토큰 암호
            decodedToken = jwt.verify(token, 'somesupersupersecretfromminjae');
          } catch (err) {
            req.isAuth = false;
            return next;
          }
          if (!decodedToken) {
            req.isAuth = false;
            return next;
          }
          // decode 됐기 때문에 이전에 설정해주었던 userId에 접근할 수 있다.
          req.userId = decodedToken.userId;
          req.isAuth = true;
          next();
        };
    4. app.js

      • 모든 요청에 토큰 상태 확인하기

        const auth = require('./middleware/auth');
        
        app.use(auth);
    5. graphql/resolvers.js

      • createPost 메소드에 토큰 검증 상태 확인하기 middleware/auth.js 에서 보내준 상태 값으로 올바른 request인지 변조된 request인지 검사합니다.

          createPost: async function ({ postInput }, req) {
            // 1. 토큰 검사하기
            if (!req.isAuth) {
              const error = new Error('토큰이 검증되지 않았습니다.');
              error.code = 401;
              throw error;
            }
              ...
          }
      • createPost전체 로직

          createPost: async function ({ postInput }, req) {
            // 1. 토큰 검사하기
            if (!req.isAuth) {
              const error = new Error('토큰이 검증되지 않았습니다.');
              error.code = 401;
              throw error;
            }
            // 2. userInput 검사하기
            const errors = [];
            if (
              validator.isEmpty(postInput.title) ||
              !validator.isLength(postInput.title, { min: 3 })
            ) {
              errors.push({ message: 'Title is invalid.' });
            }
            if (
              validator.isEmpty(postInput.content) ||
              !validator.isLength(postInput.content, { min: 3 })
            ) {
              errors.push({ message: 'content is invalid.' });
            }
            if (errors.length > 0) {
              const error = new Error('Invalid Input');
              error.data = errors;
              error.code = 422;
              throw error;
            }
            // 3. 토큰에서 저장한 user가져오기
            const user = await User.findById(req.userId);
            if (!user) {
              const error = new Error('Invalid user');
              error.code = 401;
              throw error;
            }
            // 4. mongoDB에 저장하기
            const post = new Post({
              title: postInput.title,
              imageUrl: postInput.imageUrl,
              content: postInput.content,
              creator: user,
            });
        
            const createdPost = await post.save();
        
            // 5. user에 새로만든 post 넣어주기
            user.posts.push(createdPost);
            await user.save();
              
            return {
              ...createdPost._doc,
              _id: createdPost._id.toString(),
              createdAt: createdPost.createdAt.toISOString(),
              updatedAt: createdPost.updatedAt.toISOString(),
            };
          },

    Front-End에 Post 생성하는 기능 생성하기

    1. Client: .../Feed.js

      • finishEditHandler

        finishEditHandler = (postData) => {
            this.setState({
              editLoading: true,
            });
            const formData = new FormData();
            formData.append('title', postData.title);
            formData.append('content', postData.content);
            formData.append('image', postData.image);
        
            // 1. GraphQL Query 작성하기
            const graphqlQuery = {
              query: `
              mutation {
                createPost(postInput: {title: "${postData.title}", content: "${postData.contetn}", imageUrl: "someUrl"}) {
                  _id
                  title
                  content
                  imageUrl
                  creator {
                    name
                  }
                  createdAt
                }
              }
              
              `,
            };
        	
            // 2. BackEnd Server로 Query 보내기
            fetch('http://localhost:8080/graphql', {
              method: 'POST',
              body: JSON.stringify(graphqlQuery),
              headers: {
                Authorization: 'Bearer ' + this.props.token,
                'Content-Type': 'application/json',
              },
            })
              .then((res) => {
                return res.json();
              })
              .then((resData) => {
                if (resData.errors && resData.errors[0].status === 422) {
                  throw new Error(
                    "Validation failed. Make sure the email address isn't used yet!"
                  );
                }
                if (resData.errors) {
                  throw new Error('Create user failed!');
                }
        
                console.log(resData);
                const post = {
                  _id: resData.data.createPost._id,
                  title: resData.data.createPost.title,
                  content: resData.data.createPost.content,
                  creator: resData.data.createPost.creator,
                  createdAt: resData.data.createPost.createdAt,
                };
                this.setState((prevState) => {
                  return {
                    isEditing: false,
                    editPost: null,
                    editLoading: false,
                  };
                });
              })
              .catch((err) => {
                console.log(err);
                this.setState({
                  isEditing: false,
                  editPost: null,
                  editLoading: false,
                  error: err,
                });
              });
          };

    BackEnd에 Post 가져오는 로직 작성하기

    1. graphql/schema.js

      • Query 작성하기

        module.exports = buildSchema(`
        
            ...
        
            type PostData {
                posts: [Post!]!
                totalPosts: Int!
            }
        
          ...
        
            type RootQuery {
                login(email: String!, password: String!): AuthData!
                posts: PostData!
            }
        
            schema {
                mutation: RootMutation
                query: RootQuery
            }
        `);
    2. graphql/resolvers.js

      • posts resolver 작성하기

         posts: async function (args, req) {
            if (!req.isAuth) {
              const error = new Error('권한이 없습니다.');
              error.code = 401;
              throw error;
            }
            const totalPosts = await Post.find().countDocuments();
            const posts = await Post.find().sort({ createdAt: -1 }).populate('creator');
        
            //mongodb 객체 형태로 보내주면 graphql이 읽을 수 없으므로 변환해서 보내주기
            return {
              posts: posts.map((p) => {
                return {
                  ...p._doc,
                  _id: p._id.toString(),
                  createdAt: p.createdAt.toISOString(),
                  updatedAt: p.updatedAt.toISOString(),
                };
              }),
              totalPosts: totalPosts,
            };
          },

    FrontEnd에 Post 가져오는 로직 작성하기

    1. ...Feed.js

      • loadPosts

          loadPosts = (direction) => {
            if (direction) {
              this.setState({ postsLoading: true, posts: [] });
            }
            let page = this.state.postPage;
            if (direction === 'next') {
              page++;
              this.setState({ postPage: page });
            }
            if (direction === 'previous') {
              page--;
              this.setState({ postPage: page });
            }
            // 1. graphql Query 작성하기
            const graphqlQuery = {
              query: `
        	{
              posts{
                posts{
                  _id
                  title
                  content
                  creator{
                    name
                  }
                  createdAt
                }
                totalPosts
            	}
        	}
              `,
            };
            // 2. Server로 Query 보내기
            fetch('http://localhost:8080/graphql', {
              method: 'POST',
              headers: {
                Authorization: 'Bearer ' + this.props.token,
                'Content-Type': 'application/json',
              },
              body: JSON.stringify(graphqlQuery),
            })
              // 3. response 다루기
              .then((res) => {
                return res.json();
              })
              .then((resData) => {
                if (resData.errors) {
                  throw new Error('포스트를 가져오는데 Error 발생');
                }
                this.setState({
                  posts: resData.data.posts.posts.map((post) => {
                    return {
                      ...post,
                      imagePath: post.imageUrl,
                    };
                  }),
                  totalPosts: resData.data.posts.totalPosts,
                  postsLoading: false,
                });
              })
              .catch(this.catchError);
          };
      • finishEditHandler 에서 post생성하고 즉시 업데이트하기

          finishEditHandler = (postData) => {
          	  ...
                const post = {
                  _id: resData.data.createPost._id,
                  title: resData.data.createPost.title,
                  content: resData.data.createPost.content,
                  creator: resData.data.createPost.creator,
                  createdAt: resData.data.createPost.createdAt,
                };
              // 업데이트하고 setState설정해줘서 바로 업데이트하기
                this.setState((prevState) => {
                  let updatedPosts = [...prevState.posts];
                  if (prevState.editPost) {
                    const postIndex = prevState.posts.findIndex(
                      (p) => p._id === prevState.editPosts._id
                    );
                    updatedPosts[postIndex] = post;
                  } else {
                    updatedPosts.unshift(post);
                  }
                  return {
                    posts: updatedPosts,
                    isEditing: false,
                    editPost: null,
                    editLoading: false,
                  };
                });
              })
            ....
          };

    pagination

    1. graphql/schema.js

      • posts query에 인자 넣어주기

        `posts(page: Int): PostData `
    2. graphlq/resolvers.js

      • posts

        posts: async function ({ page }, req) {
            if (!req.isAuth) {
              const error = new Error('권한이 없습니다.');
              error.code = 401;
              throw error;
            }
            // 1. page 설정이 없으면 1페이지 보여주기
            if (!page) {
              page = 1;
            }
            // 2. page 설정
            const perPage = 2;
            const totalPosts = await Post.find().countDocuments();
            const posts = await Post.find()
              .sort({ createdAt: -1 })
              .skip((page - 1) * perPage)
              .limit(perPage)
              .populate('creator');
        
            ...
        }
    3. Client:.../Feed.js

      • loadPosts posts 쿼리에 페이지 인자 넣기

        const graphqlQuery = {
              query: `
              {
              posts(page: ${page}){
                posts{
                  _id
                  title
                  content
                  creator{
                    name
                  }
                  createdAt
                }
                totalPosts
             }
            }
              `,
      • finishEditHandler에 페이지 로직에 새로 만든 게시물 스킵하기

                this.setState((prevState) => {
                  let updatedPosts = [...prevState.posts];
                  if (prevState.editPost) {
                    const postIndex = prevState.posts.findIndex(
                      (p) => p._id === prevState.editPosts._id
                    );
                    updatedPosts[postIndex] = post;
                  } else {
                    updatedPosts.pop();
                    updatedPosts.unshift(post);
                  }
                  return {
                    posts: updatedPosts,
                    isEditing: false,
                    editPost: null,
                    editLoading: false,
                  };
                });