Simple Comments with GraphQL & Vue.js
Published: 2020-05-31 18:25:09

Lately I've been getting into new frameworks and experimenting with their features while comparing them to frameworks I'm familiar with. Vue.js has been both surprisingly comfortable and exciting to learn and alongside my team, who were also relatively new to Vue, sought out to build simple common components from scratch in Vue rather than rely on too many outside libraries. 

The app required a fully realized social networking aspect, so rather than user 3rd party services we thought it would be a great exercise to dive deeper into what Vue can do given its purposely minimal out of the box functionality. After making an auth system, I set out to implement the like feature and the comments feature which provided a few interesting coding tidbits I'd like to share today. 

First I made a folder called comments in the src/components folder where others had been placing their files. We had chosen graphQL to build out our api, and knowing that making a query or mutation in graphQL can be somewhat lengthy, I went ahead and made 2 other files to give a little head room in the file size and overall file structure. So a CommentList component provided to be rendered on every post, that loads a single comment component, which inside of it contains the comment submit component. My queries happen on the submit, but by abstracting it two levels, my CommentsList component simply passes down and holds the structure of the collapsible divs to be toggled. The single comment can load the input and submit button's component, and give a fresh DOM slate for fine tuning style and alignment of the actual comment material. 

As for the code, let's take a look at the Comment Input component and what it's doing. 
```
   <template>
     <b-container>
       <b-row>
         <form ref="form" @submit.stop.prevent="handleSubmit">

             <b-form-input
               squared
               class="input-sub"
               :id="song.id"
               v-model="commentText"
               :state="commentState"
               @keypress.enter="handleComment(myId, song.id)"
             ></b-form-input>

          </form>
           <b-button
             squared
             class="comment-submit"
             @click="handleComment(myId, song.id)"
          >
            submit
           </b-button>
       </b-row>
     </b-container>
   </template>
```
Here is the top half of the template containing the html markup to receive the input data.

Then I can make a method in the scripts section that corresponds to the above form arguments and calls a function addComment on the input data. 

```
    handleSubmit(event) {
      event.preventDefault();
    },
    handleComment(userId, songId) {
      if (this.commentText) {
       this.addComment(userId, songId, this.commentText);
        this.commentText = '';
        this.commentState = null;
      } else {
        console.log('please enter a comment');
      }
    },
```
This will deal with all the front end needs, so we can now look how we set up or backend and with GraphQL/Postgres. 

Having created a comment table in the database, I set out to make the insertion function addComment that I had called on the input data to be sent to the api for insertion then receive the updated data. 

```
    addComment(userId, songId, text) {
      // console.log('like', likes);
      const query = `mutation {
      addComment(id_user: ${userId}, id_song: ${songId}, text: 
      "${text}") {
        id,
        text,
      }
    }`;
      request(`${process.env.NODE_ENV === 'development' ? 
       'http://localhost:8081' : ''}/api`, query)
        .then((res) => {
          this.newUserComment = res.addComment;
          this.$emit('new-comment');
        })
        .catch((err) => console.log(err));
    },
```

The addComment function is a method on the CommentInput component which has access to the form data in the live html page which is also conveniently located in the same file with Vue.

It takes a userID, a songId (we were building a music sharing app) and some text for the message. In graphQL, mutations are POST queries and are structured like Javascript Objects with arguments that return functions that return promises. Crafting these are visually familiar because of the object syntax and structure but also appear to be very versatile with their functional nature. In the api/ define our schema with the syntax seen below, which gives the type and structure of the expected data followed by our custom raw SQL statement. 

```
  const {
    GraphQLObjectType, GraphQLString, GraphQLID, GraphQLInt, 
    GraphQLBoolean,
  } = require('graphql');
  const { GraphQLDateTime } = require('graphql-iso-date');
   ...
  exports.CommentType = new GraphQLObjectType({
   name: 'Comment',
   type: 'Query',
   fields: {
    id: { type: GraphQLID },
    id_user: { type: GraphQLID },
    id_song: { type: GraphQLID },
    text: { type: GraphQLString },
    created_at: { type: GraphQLDateTime },
    username: { type: GraphQLString },
    url_avatar: { type: GraphQLString },
   },
  });
```
And here in the mutation file, we can create our mutation:
```
   addComment: {
      type: CommentType,
      args: {
        id: { type: GraphQLID },
        id_user: { type: GraphQLID },
        id_song: { type: GraphQLID },
        text: { type: GraphQLString },
      },
      resolve(parentValue, args) {
        const insertComment = 'INSERT INTO comment(id_user, id_song, text, created_at) VALUES ($1, $2, $3, now()) RETURNING id, text';
        return db.one(insertComment, [args.id_user, args.id_song, args.text])
          .then((data) => data)
          .catch((err) => { console.log('err', err); });
      },
    },
```

After successful connection, the resolve is called passing in our arguments, and the insert statement is declared and then returned using db.one function from graphQL. This can be called on the db with the query-string & arguments and will affect only one row. This will return a promise from the database with the id and text after successful insertion which we can then call in our then function to be used on the client's response object. 

Having Vue's lightweight and super minimal but powerful functionality in its framework made this task end up looking concise and readable with just a few lines of code on each file and can easily be built out from here. Hoping this example conveys that you can use Vue successfully for rapid prototyping, and produce scalable and re-usable developer friendly code very efficiently.