Node, Express, PostgreSQL, Vue 2 and GraphQL CRUD Web App

Node, Express, PostgreSQL, Vue 2 and GraphQL CRUD Web App

For the client side (Vue 2) we will use Vue-Apollo module. For the backend side, we will use Node, Express, Sequelize, and PostgreSQL with Express-Graphql module and their dependencies. The scenario for this tutorial is simple as usual, just the CRUD operation which data accessible through GraphQL.

Table of Contents:

  • Create Express.js Application and Install Required Modules
  • Add and Configure Sequelize.js Module and Dependencies
  • Create or Generate Models and Migrations
  • Install GraphQL Modules and Dependencies
  • Create GraphQL Schemas for the Book
  • Add Mutation for CRUD Operation to the Schema
  • Test GraphQL using GraphiQL
  • Create Vue 2 Application
  • Install Required Modules, Dependencies, and Router
  • Create a Component to Display List of Books
  • Create a Component to Show and Delete Books
  • Create a Component to Add a New Book
  • Create a Component to Edit a Book
  • Run and Test GraphQL CRUD from the Vue 2 Application

The following tools, frameworks, and modules are required for this tutorial:

  • Node.js (choose recommended version)
  • Vue 2
  • Express.js
  • GraphQL
  • Express-GraphQL
  • Vue-Apollo
  • Bootstrap-Vue
  • Terminal (Mac/Linux) or Node Command Line (Windows)
  • IDE or Text Editor (We are using Visual Studio Code)

We assume that you have already Installed Node.js. Make sure Node.js command line is working (on Windows) or runnable in Linux/OS X terminal.

<pre class="ql-syntax" spellcheck="false">node -v v10.15.1 npm -v 6.8.0 yarn -v 1.10.1 </pre>

That the versions that we are uses. Let's continue with the main steps.



1. Create Express.js Application and Install Required Modules

Open your terminal or node command line the go to your projects folder. First, install express generator using this command.

<pre class="ql-syntax" spellcheck="false">sudo npm install express-generator -g </pre>

Next, create an Express.js app using this command.

<pre class="ql-syntax" spellcheck="false">express vue-graphql </pre>

This will create Express.js project with files and directories.

<pre class="ql-syntax" spellcheck="false">create : vue-graphql/ create : vue-graphql/public/ create : vue-graphql/public/javascripts/ create : vue-graphql/public/images/ create : vue-graphql/public/stylesheets/ create : vue-graphql/public/stylesheets/style.css create : vue-graphql/routes/ create : vue-graphql/routes/index.js create : vue-graphql/routes/users.js create : vue-graphql/views/ create : vue-graphql/views/error.jade create : vue-graphql/views/index.jade create : vue-graphql/views/layout.jade create : vue-graphql/app.js create : vue-graphql/package.json create : vue-graphql/bin/ create : vue-graphql/bin/www </pre>

Next, go to the newly created project folder then install node modules.

<pre class="ql-syntax" spellcheck="false">cd vue-graphql && npm install </pre>

There's no view yet using the latest Express generator. We don't need it because we will create a GraphQL server.

2. Add and Configure Sequelize.js Module and Dependencies

Before installing the modules for this project, first, install Sequelize-CLI by type this command.

<pre class="ql-syntax" spellcheck="false">sudo npm install -g sequelize-cli </pre>

To install Sequelize.js module, type this command.

<pre class="ql-syntax" spellcheck="false">npm install --save sequelize </pre>

Then install the module for PostgreSQL.

<pre class="ql-syntax" spellcheck="false">npm install --save pg pg-hstore </pre>

Next, create a new file at the root of the project folder.

<pre class="ql-syntax" spellcheck="false">touch .sequelizerc </pre>

Open and edit that file then add these lines of codes.

<pre class="ql-syntax" spellcheck="false">const path = require('path');

module.exports = {
  "config": path.resolve('./config', 'config.json'),
  "models-path": path.resolve('./models'),
  "seeders-path": path.resolve('./seeders'),
  "migrations-path": path.resolve('./migrations')
};
</pre>

That files will tell Sequelize initialization to generate config, models, seeders and migrations files to specific directories. Next, type this command to initialize the Sequelize.

<pre class="ql-syntax" spellcheck="false">sequelize init
</pre>

That command will create config/config.json, models/index.js, migrations and seeders directories and files. Next, open and edit config/config.json then make it like this.

<pre class="ql-syntax" spellcheck="false">{
  "development": {
    "username": "djamware",
    "password": "[email protected]@r3",
    "database": "node_sequelize",
    "host": "127.0.0.1",
    "dialect": "postgres"
  },
  "test": {
    "username": "root",
    "password": "[email protected]@r3",
    "database": "node_sequelize",
    "host": "127.0.0.1",
    "dialect": "postgres"
  },
  "production": {
    "username": "root",
    "password": "[email protected]@r3",
    "database": "node_sequelize",
    "host": "127.0.0.1",
    "dialect": "postgres"
  }
}
</pre>

We use the same configuration for all the environment because we are using the same machine, server, and database for this tutorial.

Before run and test connection, make sure you have created a database as described in the above configuration. You can use the psql command to create a user and database.

<pre class="ql-syntax" spellcheck="false">psql postgres --u postgres
</pre>

Next, type this command for creating a new user with password then give access for creating the database.

<pre class="ql-syntax" spellcheck="false">postgres-# CREATE ROLE djamware WITH LOGIN PASSWORD '[email protected]@r3';
postgres-# ALTER ROLE djamware CREATEDB;
</pre>

Quit psql then log in again using the new user that previously created.

<pre class="ql-syntax" spellcheck="false">postgres-# \q
psql postgres -U djamware
</pre>

Enter the password, then you will enter this psql console.

<pre class="ql-syntax" spellcheck="false">psql (9.5.13)
Type "help" for help.

postgres=>
</pre>

Type this command to creating a new database.

<pre class="ql-syntax" spellcheck="false">postgres=> CREATE DATABASE book_store;
</pre>

Then give that new user privileges to the new database then quit the psql.

<pre class="ql-syntax" spellcheck="false">postgres=> GRANT ALL PRIVILEGES ON DATABASE book_store TO djamware;
postgres=> \q
</pre>


3. Create or Generate Models and Migrations

We will use Sequelize-CLI to generate a new model. Type this command to create a model for 'Book'.

<pre class="ql-syntax" spellcheck="false">sequelize model:generate --name Book --attributes isbn:string,title:string,author:string,description:string,publishedYear:integer,publisher:string
</pre>

That commands will generate models and migration files. The content of the model file looks like this.

<pre class="ql-syntax" spellcheck="false">'use strict';
module.exports = (sequelize, DataTypes) => {
  const Book = sequelize.define('Book', {
    isbn: DataTypes.STRING,
    title: DataTypes.STRING,
    author: DataTypes.STRING,
    description: DataTypes.STRING,
    publishedYear: DataTypes.INTEGER,
    publisher: DataTypes.STRING
  }, {});
  Book.associate = function(models) {
    // associations can be defined here
  };
  return Book;
};
</pre>

And the migration file looks like this.

<pre class="ql-syntax" spellcheck="false">'use strict';
module.exports = {
  up: (queryInterface, Sequelize) => {
    return queryInterface.createTable('Books', {
      id: {
        allowNull: false,
        autoIncrement: true,
        primaryKey: true,
        type: Sequelize.INTEGER
      },
      isbn: {
        type: Sequelize.STRING
      },
      title: {
        type: Sequelize.STRING
      },
      author: {
        type: Sequelize.STRING
      },
      description: {
        type: Sequelize.STRING
      },
      publishedYear: {
        type: Sequelize.INTEGER
      },
      publisher: {
        type: Sequelize.STRING
      },
      createdAt: {
        allowNull: false,
        type: Sequelize.DATE
      },
      updatedAt: {
        allowNull: false,
        type: Sequelize.DATE
      }
    });
  },
  down: (queryInterface, Sequelize) => {
    return queryInterface.dropTable('Books');
  }
};
</pre>

Finally, for migrations, there's nothing to change and they all ready to generate the table to the PostgreSQL Database. Type this command to generate the table to the database.

<pre class="ql-syntax" spellcheck="false">sequelize db:migrate
</pre>


4. Install GraphQL Modules and Dependencies

Now, the GraphQL time. Type this command to install GraphQL modules and it's dependencies.

<pre class="ql-syntax" spellcheck="false">npm install express express-graphql graphql graphql-date cors --save
</pre>

Next, open and edit app.js then declare all of those modules and dependencies.

<pre class="ql-syntax" spellcheck="false">var graphqlHTTP = require('express-graphql');
var schema = require('./graphql/bookSchemas');
var cors = require("cors");
</pre>

The schema is not created yet, we will create it in the next steps. Next, add these lines of codes for configuring GraphQL that can use over HTTP.

<pre class="ql-syntax" spellcheck="false">app.use('*', cors());
app.use('/graphql', cors(), graphqlHTTP({
  schema: schema,
  rootValue: global,
  graphiql: true,
}));
</pre>

That's configuration are enabled CORS and the GraphiQL. GraphiQL is the user interface for testing GraphQL query.



5. Create GraphQL Schemas for the Book

Create a folder at the server folder for hold GraphQL Schema files then create a Javascript file for the schema.

<pre class="ql-syntax" spellcheck="false">mkdir graphql
touch graphql/bookSchemas.js
</pre>

Next, open and edit server/graphql/bookSchemas.js then declares all required modules and models.

<pre class="ql-syntax" spellcheck="false">var GraphQLSchema = require('graphql').GraphQLSchema;
var GraphQLObjectType = require('graphql').GraphQLObjectType;
var GraphQLList = require('graphql').GraphQLList;
var GraphQLObjectType = require('graphql').GraphQLObjectType;
var GraphQLNonNull = require('graphql').GraphQLNonNull;
var GraphQLID = require('graphql').GraphQLID;
var GraphQLString = require('graphql').GraphQLString;
var GraphQLInt = require('graphql').GraphQLInt;
var GraphQLDate = require('graphql-date');
var BookModel = require('../models').Book;
</pre>

Create a GraphQL Object Type for Book models.

<pre class="ql-syntax" spellcheck="false">var bookType = new GraphQLObjectType({
  name: "book",
  fields: function() {
    return {
      id: {
        type: GraphQLInt
      },
      isbn: {
        type: GraphQLString
      },
      title: {
        type: GraphQLString
      },
      author: {
        type: GraphQLString
      },
      description: {
        type: GraphQLString
      },
      publishedYear: {
        type: GraphQLInt
      },
      publisher: {
        type: GraphQLString
      },
      createdAt: {
        type: GraphQLDate
      },
      updatedAt: {
        type: GraphQLDate
      }
    };
  }
});
</pre>

Next, create a GraphQL query type that calls a list of book and single book by ID.

<pre class="ql-syntax" spellcheck="false">var queryType = new GraphQLObjectType({
  name: 'Query',
  fields: function () {
    return {
      books: {
        type: new GraphQLList(bookType),
        resolve: function () {
          const books = BookModel.findAll({
            order: [
              ['createdAt', 'DESC']
            ],
          })
          if (!books) {
            throw new Error('Error')
          }
          return books
        }
      },
      book: {
        type: bookType,
        args: {
          id: {
            name: 'id',
            type: GraphQLString
          }
        },
        resolve: function (root, params) {
          const bookDetails = BookModel.findByPk(params.id).exec()
          if (!bookDetails) {
            throw new Error('Error')
          }
          return bookDetails
        }
      }
    }
  }
});
</pre>

Finally, exports this file as GraphQL schema by adding this line at the end of the file.

<pre class="ql-syntax" spellcheck="false">module.exports = new GraphQLSchema({query: queryType});
</pre>


6. Add Mutation for CRUD Operation to the Schema

For completing CRUD (Create, Read, Update, Delete) operation of the GraphQL, we need to add a mutation that contains create, update and delete operations. Open and edit graphql/bookSchemas.js then add this mutation as GraphQL Object Type.

<pre class="ql-syntax" spellcheck="false">var mutation = new GraphQLObjectType({
  name: 'Mutation',
  fields: function () {
    return {
      addBook: {
        type: bookType,
        args: {
          isbn: {
            type: new GraphQLNonNull(GraphQLString)
          },
          title: {
            type: new GraphQLNonNull(GraphQLString)
          },
          author: {
            type: new GraphQLNonNull(GraphQLString)
          },
          description: {
            type: new GraphQLNonNull(GraphQLString)
          },
          publishedYear: {
            type: new GraphQLNonNull(GraphQLInt)
          },
          publisher: {
            type: new GraphQLNonNull(GraphQLString)
          }
        },
        resolve: function (root, params) {
          const bookModel = new BookModel(params);
          const newBook = bookModel.save();
          if (!newBook) {
            throw new Error('Error');
          }
          return newBook
        }
      },
      updateBook: {
        type: bookType,
        args: {
          id: {
            name: 'id',
            type: new GraphQLNonNull(GraphQLInt)
          },
          isbn: {
            type: new GraphQLNonNull(GraphQLString)
          },
          title: {
            type: new GraphQLNonNull(GraphQLString)
          },
          author: {
            type: new GraphQLNonNull(GraphQLString)
          },
          description: {
            type: new GraphQLNonNull(GraphQLString)
          },
          publishedYear: {
            type: new GraphQLNonNull(GraphQLInt)
          },
          publisher: {
            type: new GraphQLNonNull(GraphQLString)
          }
        },
        resolve(root, params) {
          return BookModel
          .findByPk(params.id)
          .then(book => {
            if (!book) {
              throw new Error('Not found');
            }
            return book
              .update({
                isbn: params.isbn || book.isbn,
                title: params.title || book.title,
                author: params.author || book.author,
                description: params.description || book.description,
                publishedYear: params.publishedYear || book.publishedYear,
                publisher: params.publisher || book.publisher,
              })
              .then(() => { return book; })
              .catch((error) => { throw new Error(error); });
          })
          .catch((error) => { throw new Error(error); });
        }
      },
      removeBook: {
        type: bookType,
        args: {
          id: {
            type: new GraphQLNonNull(GraphQLInt)
          }
        },
        resolve(root, params) {
          return BookModel
          .findByPk(params.id)
          .then(book => {
            if (!book) {
              throw new Error('Not found');
            }
            return book
              .destroy()
              .then(() => { return book; })
              .catch((error) => { throw new Error(error); });
          })
          .catch((error) => { throw new Error(error); });
        }
      }
    }
  }
});
</pre>

Finally, add this mutation to the GraphQL Schema exports like below.

<pre class="ql-syntax" spellcheck="false">module.exports = new GraphQLSchema({query: queryType, mutation: mutation});
</pre>


7. Test GraphQL using GraphiQL

To test the queries and mutations of CRUD operations, re-run again the Express.js app then open the browser. Go to this address <a href="http://localhost:3000/graphql" target="_blank">http://localhost:3000/graphql</a> to open the GraphiQL User Interface.

To get the list of books, replace all of the text on the left pane with this GraphQL query then click the Play button.

To get a single book by ID, use this GraphQL query.

<pre class="ql-syntax" spellcheck="false">{
  book(id: 1) {
    id
    isbn
    title
    author
    description
    publishedYear
    publisher
    updatedAt
  }
}
</pre>

To add a book, use this GraphQL mutation.

<pre class="ql-syntax" spellcheck="false">mutation {
  addBook(
    isbn: "12345678",
    title: "Whatever this Book Title",
    author: "Mr. Bean",
    description: "The short explanation of this Book",
    publisher: "Djamware Press",
    publishedYear: 2019
  ) {
    updatedAt
  }
}
</pre>

You will the response at the right pane like this.

<pre class="ql-syntax" spellcheck="false">{
  "data": {
    "addBook": {
      "updatedAt": "2019-02-26T13:55:39.160Z"
    }
  }
}
</pre>

To update a book, use this GraphQL mutation.

<pre class="ql-syntax" spellcheck="false">mutation {
  updateBook(
    id: 1,
    isbn: "12345678221",
    title: "The Learning Curve of GraphQL",
    author: "Didin J.",
    description: "The short explanation of this Book",
    publisher: "Djamware Press",
    publishedYear: 2019
  ) {
    id,
    updatedAt
  }
}
</pre>

You will see the response in the right pane like this.

<pre class="ql-syntax" spellcheck="false">{
  "data": {
    "updateBook": {
      "id": 1,
      "updated_date": "2019-02-26T13:58:35.811Z"
    }
  }
}
</pre>


To delete a book by ID, use this GraphQL mutation.

<pre class="ql-syntax" spellcheck="false">mutation {
  removeBook(id: 1) {
    id
  }
}
</pre>

You will see the response in the right pane like this.

<pre class="ql-syntax" spellcheck="false">{
  "data": {
    "removeBook": {
      "id": 1
    }
  }
}
</pre>


8. Create Vue 2 Application

To install Vue-CLI type this command from the Terminal or Node command line.

<pre class="ql-syntax" spellcheck="false">sudo npm install -g @vue/cli
</pre>

or

<pre class="ql-syntax" spellcheck="false">yarn global add @vue/cli
</pre>

Next, check the version to make sure that you have the 3.x version of Vue-CLI.

<pre class="ql-syntax" spellcheck="false">vue --version
3.7.0
</pre>

Next, create a new Vue.js project by type this command.

<pre class="ql-syntax" spellcheck="false">vue create client
</pre>

For now, use the default for every question that shows up in the Terminal. Next, go to the newly created folder.

<pre class="ql-syntax" spellcheck="false">cd ./client
</pre>

To make sure that created Vue.js project working, type this command to run the Vue.js application.

<pre class="ql-syntax" spellcheck="false">npm run serve
</pre>

or

<pre class="ql-syntax" spellcheck="false">yarn serve
</pre>

You will see this page when open <a href="http://localhost:8080/" target="_blank">http://localhost:8080/</a> in the browser.


9. Install/Configure the Required Modules, Dependencies, and Router

Now, we have to install and configure all of the required modules and dependencies. Type this command to install the modules.

<pre class="ql-syntax" spellcheck="false">npm install apollo-boost vue-apollo graphql-tag graphql vue-router --save
</pre>

Next, open and edit src/main.js then add these imports.

<pre class="ql-syntax" spellcheck="false">import ApolloClient from "apollo-boost";
import VueApollo from "vue-apollo";
</pre>

Add these constant variables then register VueApollo in Vue 2 app.

<pre class="ql-syntax" spellcheck="false">const apolloClient = new ApolloClient({
  uri: 'http://localhost:3000/graphql'
});

const apolloProvider = new VueApollo({
  defaultClient: apolloClient
});

Vue.use(VueApollo);

new Vue({
  apolloProvider,
  render: h => h(App)
}).$mount('#app')
</pre>

To register or create routes for the whole application navigation, create a router folder and index.js file.

<pre class="ql-syntax" spellcheck="false">mkdir src/router
touch src/router/index.js
</pre>

Open and edit src/router/index.js then add these imports.

<pre class="ql-syntax" spellcheck="false">import VueRouter from 'vue-router'
import BookList from '@/components/BookList'
import ShowBook from '@/components/ShowBook'
import AddBook from '@/components/AddBook'
import EditBook from '@/components/EditBook'
</pre>

Add the router to each component or page.

<pre class="ql-syntax" spellcheck="false">export default new VueRouter({
  routes: [
    {
      path: '/',
      name: 'BookList',
      component: BookList
    },
    {
      path: '/show-book/:id',
      name: 'ShowBook',
      component: ShowBook
    },
    {
      path: '/add-book',
      name: 'AddBook',
      component: AddBook
    },
    {
      path: '/edit-book/:id',
      name: 'EditBook',
      component: EditBook
    }
  ]
})
</pre>

Add Vue files for above-registered components or pages.

<pre class="ql-syntax" spellcheck="false">touch src/components/BookList.vue
touch src/components/ShowBook.vue
touch src/components/AddBook.vue
touch src/components/EditBook.vue
</pre>

Finally, add or register this router file to src/main.js by adding these imports.

<pre class="ql-syntax" spellcheck="false">import VueRouter from 'vue-router'
import router from './router'
</pre>

Register the Vue-Router after Vue.config.

<pre class="ql-syntax" spellcheck="false">Vue.use(VueRouter)
</pre>

Modify new Vue to be like this.

<pre class="ql-syntax" spellcheck="false">new Vue({
  apolloProvider,
  router,
  render: h => h(App)
}).$mount('#app')
</pre>


10. Create a Component to Display List of Books

Before create or show data to the views, we have to add Bootstrap-Vue. Type this command to install the module.

<pre class="ql-syntax" spellcheck="false">npm i bootstrap-vue
</pre>

Next, open and edit src/main.js then add these imports.

<pre class="ql-syntax" spellcheck="false">import BootstrapVue from 'bootstrap-vue'
import 'bootstrap/dist/css/bootstrap.css'
import 'bootstrap-vue/dist/bootstrap-vue.css'
</pre>

Add this line after Vue.config.

<pre class="ql-syntax" spellcheck="false">Vue.use(BootstrapVue);
</pre>

Now, open and edit src/components/BookList.vue then add this template tags that contain a bootstrap-vue table.

<pre class="ql-syntax" spellcheck="false"><template>
  <b-row>
    <b-col cols="12">
      <h2>
        Book List
        <b-link href="#/add-Book">(Add Book)</b-link>
      </h2>
      <b-table striped hover :items="books" :fields="fields">
        <template slot="actions" scope="row">
          <b-btn size="sm" @click.stop="details(row.item)">Details</b-btn>
        </template>
      </b-table>
    </b-col>
  </b-row>
</template>
</pre>

Next, add the script tag for hold all Vue 2 codes.

<pre class="ql-syntax" spellcheck="false"><script></script>
</pre>

Inside the script tag, add these imports.

<pre class="ql-syntax" spellcheck="false">import gql from "graphql-tag";
import router from "../router";
</pre>

Declare the constant variables for GraphQL query.

<pre class="ql-syntax" spellcheck="false">const GET_BOOKS = gql&nbsp; { &nbsp; &nbsp; books { &nbsp; &nbsp; &nbsp; id &nbsp; &nbsp; &nbsp; title &nbsp; &nbsp; &nbsp; author &nbsp; &nbsp; } &nbsp; };
</pre>

Add the main Vue 2 export that contains Vue-Apollo calls that filled Vue 2 data. 

<pre class="ql-syntax" spellcheck="false">export default {
  name: "BookList",
  apollo: {
    books: {
      query: GET_BOOKS,
      pollInterval: 300
    }
  },
  data() {
    return {
      fields: {
        title: { label: "Title", sortable: true, class: "text-left" },
        author: { label: "Author", sortable: true, class: "text-left" },
        actions: { label: "Action", class: "text-center" }
      },
      books: []
    };
  },
  methods: {
    details(book) {
      router.push({ name: "ShowBook", params: { id: book.id } });
    }
  }
};
</pre>

Finally, add the style tag for styling the template.

<pre class="ql-syntax" spellcheck="false"><style>
.table {
  width: 96%;
  margin: 0 auto;
}
</style>
</pre>


11. Create a Component to Show and Delete Books

To show the book details that contains all book detail, edit and delete buttons, open and edit src/components/ShowBook.vue then add these template tags that contain a bootstrap-vue component for display the details.

<pre class="ql-syntax" spellcheck="false"><template>
  <b-row>
    <b-col cols="12">
      <h2>
        Book List
        <b-link href="#/">(Book List)</b-link>
      </h2>
      <b-jumbotron>
        <template slot="header">{{book.title}}</template>
        <template slot="lead">
          ISBN: {{book.isbn}}
          <br>
          Author: {{book.author}}
          <br>
          Description: {{book.description}}
          <br>
          Published Year: {{book.publishedYear}}
          <br>
          Publisher: {{book.publisher}}
          <br>
          Update At: {{book.updatedAt}}
          <br>
        </template>
        <hr class="my-4">
        <b-btn class="edit-btn" variant="success" @click.stop="editBook(book.id)">Edit</b-btn>
        <b-btn variant="danger" @click.stop="deleteBook(book.id)">Delete</b-btn>
      </b-jumbotron>
    </b-col>
  </b-row>
</template>
</pre>

Next, add the script tag.

<pre class="ql-syntax" spellcheck="false"><script></script>
</pre>

Inside the script tag, add these imports.

<pre class="ql-syntax" spellcheck="false">import gql from "graphql-tag";
import router from "../router";
</pre>

Declare the constant variables that handle get a single book and delete book queries.

<pre class="ql-syntax" spellcheck="false">const GET_BOOK = gql&nbsp; query book($bookId: Int) { &nbsp; &nbsp; book(id: $bookId) { &nbsp; &nbsp; &nbsp; id &nbsp; &nbsp; &nbsp; isbn &nbsp; &nbsp; &nbsp; title &nbsp; &nbsp; &nbsp; author &nbsp; &nbsp; &nbsp; description &nbsp; &nbsp; &nbsp; publishedYear &nbsp; &nbsp; &nbsp; publisher &nbsp; &nbsp; &nbsp; updatedAt &nbsp; &nbsp; } &nbsp; };

const DELETE_BOOK = gql&nbsp; mutation removeBook($id: Int!) { &nbsp; &nbsp; removeBook(id: $id) { &nbsp; &nbsp; &nbsp; id &nbsp; &nbsp; } &nbsp; };
</pre>

Inside main Vue export, add all required functions, variables, and Vue-Apollo function.

<pre class="ql-syntax" spellcheck="false">export default {
  name: "ShowBook",
  data() {
    return {
      book: '',
      bookId: parseInt(this.$route.params.id)
    };
  },
  apollo: {
    book: {
      query: GET_BOOK,
      pollInterval: 300,
      variables() {
        return {
          bookId: this.bookId
        };
      }
    }
  },
  methods: {
    editBook(id) {
      router.push({
        name: "EditBook",
        params: { id: id }
      });
    },
    deleteBook(id) {
      this.$apollo
        .mutate({
          mutation: DELETE_BOOK,
          variables: {
            id: id
          }
        })
        .then(data => {
          console.log(data);
        })
        .catch(error => {
          console.error(error);
        });
    }
  }
};
</pre>

Finally, add the style tags to give the view some styles.

<pre class="ql-syntax" spellcheck="false"><style>
.jumbotron {
  padding: 2rem;
}
.edit-btn {
  margin-right: 20px;
  width: 70px;
}
</style>
</pre>


12. Create a Component to Add a New Book

To add a new book, open and edit src/components/AddBook.vue then add this Vue 2 template tag that contains a bootstrap-vue form.

<pre class="ql-syntax" spellcheck="false"><template>
  <b-row>
    <b-col cols="12">
      <h2>
        Add Book
        <b-link href="#/">(Book List)</b-link>
      </h2>
      <b-jumbotron>
        <b-form @submit="onSubmit">
          <b-form-group
            id="fieldsetHorizontal"
            horizontal
            :label-cols="4"
            breakpoint="md"
            label="Enter ISBN"
          >
            <b-form-input id="isbn" v-model.trim="book.isbn"></b-form-input>
          </b-form-group>
          <b-form-group
            id="fieldsetHorizontal"
            horizontal
            :label-cols="4"
            breakpoint="md"
            label="Enter Title"
          >
            <b-form-input id="title" v-model.trim="book.title"></b-form-input>
          </b-form-group>
          <b-form-group
            id="fieldsetHorizontal"
            horizontal
            :label-cols="4"
            breakpoint="md"
            label="Enter Author"
          >
            <b-form-input id="author" v-model.trim="book.author"></b-form-input>
          </b-form-group>
          <b-form-group
            id="fieldsetHorizontal"
            horizontal
            :label-cols="4"
            breakpoint="md"
            label="Enter Description"
          >
            <b-form-textarea
              id="description"
              v-model="book.description"
              placeholder="Enter something"
              :rows="2"
              :max-rows="6"
            >{{book.description}}</b-form-textarea>
          </b-form-group>
          <b-form-group
            id="fieldsetHorizontal"
            horizontal
            :label-cols="4"
            breakpoint="md"
            label="Enter Publisher"
          >
            <b-form-input id="publisher" v-model.trim="book.publisher"></b-form-input>
          </b-form-group>
          <b-form-group
            id="fieldsetHorizontal"
            horizontal
            :label-cols="4"
            breakpoint="md"
            label="Enter Published Year"
          >
            <b-form-input type="number" id="publishedYear" v-model.trim="book.publishedYear"></b-form-input>
          </b-form-group>
          <b-button type="submit" variant="primary">Save</b-button>
        </b-form>
      </b-jumbotron>
    </b-col>
  </b-row>
</template>
</pre>

Next, add the script tag.

<pre class="ql-syntax" spellcheck="false"><script></script>
</pre>

Inside the script, tag adds Vue 2 codes that contain Vue-Apollo GraphQL mutation to save a new book.

<pre class="ql-syntax" spellcheck="false">import gql from "graphql-tag";
import router from "../router";

const ADD_BOOK = gql&nbsp; mutation AddBook( &nbsp; &nbsp; $isbn: String! &nbsp; &nbsp; $title: String! &nbsp; &nbsp; $author: String! &nbsp; &nbsp; $description: String! &nbsp; &nbsp; $publisher: String! &nbsp; &nbsp; $publishedYear: Int! &nbsp; ) { &nbsp; &nbsp; addBook( &nbsp; &nbsp; &nbsp; isbn: $isbn &nbsp; &nbsp; &nbsp; title: $title &nbsp; &nbsp; &nbsp; author: $author &nbsp; &nbsp; &nbsp; description: $description &nbsp; &nbsp; &nbsp; publisher: $publisher &nbsp; &nbsp; &nbsp; publishedYear: $publishedYear &nbsp; &nbsp; ) { &nbsp; &nbsp; &nbsp; id &nbsp; &nbsp; } &nbsp; };

export default {
  name: "AddBook",
  data() {
    return {
      book: {}
    };
  },
  methods: {
    onSubmit(evt) {
      evt.preventDefault();

      this.$apollo
        .mutate({
          mutation: ADD_BOOK,
          variables: {
            isbn: this.book.isbn,
            title: this.book.title,
            author: this.book.author,
            description: this.book.description,
            publisher: this.book.publisher,
            publishedYear: parseInt(this.book.publishedYear)
          }
        })
        .then(data => {
          console.log(data);
          router.push({ name: "BookList" });
        })
        .catch(error => {
          console.error(error);
        });
    }
  }
};
</pre>

Finally, give the view a style by adding the style tag.

<pre class="ql-syntax" spellcheck="false"><style>
.jumbotron {
  padding: 2rem;
}
</style>
</pre>


13. Create a Component to Edit a Book

To edit a book after getting single book data, open and edit src/components/EditBook.vue then add this Vue 2 template that contains a bootstrap-vue form.

<pre class="ql-syntax" spellcheck="false"><template>
  <b-row>
    <b-col cols="12">
      <h2>
        Edit Book
        <router-link :to="{ name: 'ShowBook', params: { id: bookId } }">(Show Book)</router-link>
      </h2>
      <b-jumbotron>
        <b-form @submit="onSubmit">
          <b-form-group
            id="fieldsetHorizontal"
            horizontal
            :label-cols="4"
            breakpoint="md"
            label="Enter ISBN"
          >
            <b-form-input id="isbn" v-model.trim="book.isbn"></b-form-input>
          </b-form-group>
          <b-form-group
            id="fieldsetHorizontal"
            horizontal
            :label-cols="4"
            breakpoint="md"
            label="Enter Title"
          >
            <b-form-input id="title" v-model.trim="book.title"></b-form-input>
          </b-form-group>
          <b-form-group
            id="fieldsetHorizontal"
            horizontal
            :label-cols="4"
            breakpoint="md"
            label="Enter Author"
          >
            <b-form-input id="author" v-model.trim="book.author"></b-form-input>
          </b-form-group>
          <b-form-group
            id="fieldsetHorizontal"
            horizontal
            :label-cols="4"
            breakpoint="md"
            label="Enter Description"
          >
            <b-form-textarea
              id="description"
              v-model="book.description"
              placeholder="Enter something"
              :rows="2"
              :max-rows="6"
            >{{book.description}}</b-form-textarea>
          </b-form-group>
          <b-form-group
            id="fieldsetHorizontal"
            horizontal
            :label-cols="4"
            breakpoint="md"
            label="Enter Publisher"
          >
            <b-form-input id="publisher" v-model.trim="book.publisher"></b-form-input>
          </b-form-group>
          <b-form-group
            id="fieldsetHorizontal"
            horizontal
            :label-cols="4"
            breakpoint="md"
            label="Enter Published Year"
          >
            <b-form-input type="number" id="publishedYear" v-model.trim="book.publishedYear"></b-form-input>
          </b-form-group>
          <b-button type="submit" variant="primary">Update</b-button>
        </b-form>
      </b-jumbotron>
    </b-col>
  </b-row>
</template>
</pre>

Next, add the script tag that contains all required Vue 2 codes with get data and update function.

<pre class="ql-syntax" spellcheck="false"><script>
import gql from "graphql-tag";
import router from "../router";

const GET_BOOK = gql&nbsp; query book($bookId: Int) { &nbsp; &nbsp; book(id: $bookId) { &nbsp; &nbsp; &nbsp; id &nbsp; &nbsp; &nbsp; isbn &nbsp; &nbsp; &nbsp; title &nbsp; &nbsp; &nbsp; author &nbsp; &nbsp; &nbsp; description &nbsp; &nbsp; &nbsp; publishedYear &nbsp; &nbsp; &nbsp; publisher &nbsp; &nbsp; } &nbsp; };

const UPDATE_BOOK = gql&nbsp; mutation updateBook( &nbsp; &nbsp; $id: Int! &nbsp; &nbsp; $isbn: String! &nbsp; &nbsp; $title: String! &nbsp; &nbsp; $author: String! &nbsp; &nbsp; $description: String! &nbsp; &nbsp; $publisher: String! &nbsp; &nbsp; $publishedYear: Int! &nbsp; ) { &nbsp; &nbsp; updateBook( &nbsp; &nbsp; &nbsp; id: $id &nbsp; &nbsp; &nbsp; isbn: $isbn &nbsp; &nbsp; &nbsp; title: $title &nbsp; &nbsp; &nbsp; author: $author &nbsp; &nbsp; &nbsp; description: $description &nbsp; &nbsp; &nbsp; publisher: $publisher &nbsp; &nbsp; &nbsp; publishedYear: $publishedYear &nbsp; &nbsp; ) { &nbsp; &nbsp; &nbsp; updatedAt &nbsp; &nbsp; } &nbsp; };

export default {
  name: "EditBook",
  data() {
    return {
      bookId: this.$route.params.id,
      book: {}
    };
  },
  apollo: {
    book: {
      query: GET_BOOK,
      variables() {
        return {
          bookId: this.bookId
        };
      }
    }
  },
  methods: {
    onSubmit(evt) {
      evt.preventDefault();

      this.$apollo
        .mutate({
          mutation: UPDATE_BOOK,
          variables: {
            id: parseInt(this.book.id),
            isbn: this.book.isbn,
            title: this.book.title,
            author: this.book.author,
            description: this.book.description,
            publisher: this.book.publisher,
            publishedYear: parseInt(this.book.publishedYear)
          }
        })
        .then(data => {
          console.log(data);
          router.push({
            name: "ShowBook",
            params: { id: this.$route.params.id }
          });
        })
        .catch(error => {
          console.error(error);
        });
    }
  }
};
</script>
</pre>


Finally, give the view some style by adding the style tag.

<pre class="ql-syntax" spellcheck="false"><style>
.jumbotron {
  padding: 2rem;
}
</style>
</pre>


14. Run and Test GraphQL CRUD from the Vue 2 Application

We assume the PostgreSQL server already running, so you just can run Node/Express.js application and Vue 2 app in the separate terminal tabs.

<pre class="ql-syntax" spellcheck="false">nodemon
cd client
npm run serve
</pre>

Next, open the browser then go to this address localhost:8080 and you should see these pages.

That it's, the Node, Express, PostgreSQL, Vue 2 and Graphql CRUD Web App. You can find the full source code on our GitHub.

Thanks for reading

If you liked this post, share it with all of your programming buddies!

Follow us on Facebook | Twitter

Learn More

The Complete Node.js Developer Course (3rd Edition)

Angular & NodeJS - The MEAN Stack Guide

NodeJS - The Complete Guide (incl. MVC, REST APIs, GraphQL)

Node.js: The Complete Guide to Build RESTful APIs (2018)

Vue JS 2 - The Complete Guide (incl. Vue Router & Vuex)

Nuxt.js - Vue.js on Steroids

Build Web Apps with Vue JS 2 & Firebase

Build a CRUD App with ASP.NET MVC and Entity Framework

Build a Basic CRUD App with PHP and MySQL

Angular 8 + Spring Boot 2.2: Build a CRUD App Today!

Build a Basic CRUD App with Vue.js and Node

Build a CRUD Web App With Python and Flask

Node, Express, React.js, Graphql and MongoDB CRUD Web Application

Node, Express, Angular 7, GraphQL and MongoDB CRUD Web App

Originally published on https://www.djamware.com

How to setting up Node API with Typescript

How to setting up Node API with Typescript

Note: You should have Nodejs installed on your machine.

First thing is to create our project folder and initialize it with npm to generate the package.json file.

Install dependencies

npm i express --save
npm i @types/node @types/express ts-node typescript nodemon --save-dev

Create a tsconfig.json file in the root of your application or run npx tsc --init on your terminal and add the configuration below.

{ 
"compilerOptions":
  {
  "target": "es6",
  "module": "commonjs",
  "allowJs": true,
  "outDir": "./build",
  "rootDir": "./src",
  "esModuleInterop": true
  }
}

Note: More options can be added to the tsconfig.json file.
Find out more here.

Add scripts to package.json file.

"scripts": 
  {
    "dev": "nodemon src/app.ts",
    	"start": "tsc && node build/app"
    }

Create a src directory where our application would be built. Inside the src directory, create an app.ts file.

Inside the app.ts file, add the code below.

import express, { Application, Request, Response, NextFunction } from "express";

const app: Application = express();

app.use(express.json());

app.get("/", (req: Request, res: Response): object => {
    return res.json({ status: "success", message: "Welcome to API Service" });
  }
);

app.use((req: Request, res: Response, next: NextFunction) => {
  const error = new Error("Route Not found");
  next(error);
});

app.use((error: { message: string; status: number }, req: Request, res: Response,next: NextFunction
  ) => {
    res.status(error.status || 500);
    res.json({
      status: "error",
      message: error.message
    });
    next();
  }
);

const PORT: any = process.env.PORT || 3000;

app.listen(PORT, () => console.log(`app listening on port ${PORT}`));

At this point, your project structure should look like the image below.

This is image title

Development

To run the application on the development environment, run the command below

npm run dev

Note: The above command compiles the files found in the src directory in memory.

Production

To run the application on the production environment, run the command below

npm start

Note: The above command compiles the files found in the src directory to a build directory and runs the app.js file in the build directory, as specified above in the start script in our package.json file.

The project used in this article can be found here.

Thanks for reading.

Angular 7 CRUD with Nodejs and MySQL Example

Angular 7 CRUD with Nodejs and MySQL Example

<span class="ql-cursor"></span>Below are the requirements for creating the CRUD on MEAN

  • Node.js
  • Angular CLI
  • Angular 7
  • Mysql
  • IDE or Text Editor

We assume that you have already available the above tools/frameworks and you are familiar with all the above that what individually actually does.

So now we will proceed step by step to achieve the task.

1. Update Angular CLI and Create Angular 7 Application

At first, We have to update the Angular CLI to the latest version. Open the terminal then go to the project folder and then type the below command to update the Angular CLI

<pre class="ql-syntax" spellcheck="false">sudo npm install -g @angular/cli </pre>

Once the above task finishes, Next task is to create new angular application with below command. So go to your project folder and then type below command:

<pre class="ql-syntax" spellcheck="false">ng new angular7-crud </pre>

then go to the newly created folder of angular application with **cd /angular7-crud ** and type **ng serve. **Now, open the browser then go to <a href="http://localhost:4200" title="" target="_blank">http://localhost:4200</a> you should see this page.

2. Create a server with node.js express and Mysql for REST APIs

create a separate folder named server for server-side stuff, Then move inside folder and create server.js by typing touch server.js

Let’s have a look on the server.js file

<pre class="ql-syntax" spellcheck="false">let app = require('express')(), server = require('http').Server(app), bodyParser = require('body-parser') express = require('express'), cors = require('cors'), http = require('http'), path = require('path');   let articleRoute = require('./Routes/article'), util = require('./Utilities/util');   app.use(bodyParser.json()); app.use(bodyParser.urlencoded({extended: false }));   app.use(cors());   app.use(function(err, req, res, next) { return res.send({ "statusCode": util.statusCode.ONE, "statusMessage":util.statusMessage.SOMETHING_WENT_WRONG }); });   app.use('/article', articleRoute);   // catch 404 and forward to error handler app.use(function(req, res, next) { next(); });   /*first API to check if server is running*/ app.get('*', (req, res) => { res.sendFile(path.join(__dirname, '../server/client/dist/index.html')); })     server.listen(3000,function(){ console.log('app listening on port: 3000'); }); </pre>

In the above file we can see, at the top, there are required packages for the app. Below that body parsing, middleware and routing is done.

The next task is to create routes and create a file article.js . So creating a folder name ‘Routes’ and adding article.js within it.

Add the below code for routing in article.js inside routing folder

<pre class="ql-syntax" spellcheck="false">let express = require('express'), router = express.Router(), util = require('../Utilities/util'), articleService = require('../Services/article');   /**Api to create article */ router.post('/create-article', (req, res) => { articleService.createArticle(req.body, (data) => { res.send(data); }); });   // /**Api to update article */ router.put('/update-article', (req, res) => { articleService.updateArticle(req.body, (data) => { res.send(data); }); });   // /**Api to delete the article */ router.delete('/delete-article', (req, res) => { articleService.deleteArticle(req.query, (data) => { res.send(data); }); });   /**Api to get the list of article */ router.get('/get-article', (req, res) => { documentService.getArticle(req.query, (data) => { res.send(data); }); });   // /**API to get the article by id... */ router.get('/get-article-by-id', (req, res) => { articleService.getArticleById(req.query, (data) => { res.send(data); }); });   module.exports = router; </pre>


Now create a folder named Utilities for all config, common methods and mysql connection config.

Now I am adding config values in a file named config.js

<pre class="ql-syntax" spellcheck="false">let environment = "dev";   let serverURLs = { "dev": { "NODE_SERVER": "http://localhost", "NODE_SERVER_PORT": "3000", "MYSQL_HOST": 'localhost', "MYSQL_USER": 'root', "MYSQL_PASSWORD": 'password', 'MYSQL_DATABASE': 'demo_angular7_crud', } }   let config = { "DB_URL_MYSQL": { "host": `${serverURLs[environment].MYSQL_HOST}`, "user": `${serverURLs[environment].MYSQL_USER}`, "password": `${serverURLs[environment].MYSQL_PASSWORD}`, "database": `${serverURLs[environment].MYSQL_DATABASE}` }, "NODE_SERVER_PORT": { "port": `${serverURLs[environment].NODE_SERVER_PORT}` }, "NODE_SERVER_URL": { "url": `${serverURLs[environment].NODE_SERVER}` } };   module.exports = { config: config }; </pre>

Now configure mysql connection. So I am writing the connection with database in a separate file. So creating a file named mysqkConfig.js under Utilities folder and adding the below line of code for mysql connection:

 

<pre class="ql-syntax" spellcheck="false">var config = require("../Utilities/config").config; var mysql = require('mysql'); var connection = mysql.createConnection({ host: config.DB_URL_MYSQL.host, user: config.DB_URL_MYSQL.user, password: config.DB_URL_MYSQL.password, database: config.DB_URL_MYSQL.database, });   connection.connect(() => { require('../Models/Article').initialize(); });   let getDB = () => { return connection; }   module.exports = { getDB: getDB } </pre>

Now I am creating separate file name util.js to save common methods and common status code/message:

<pre class="ql-syntax" spellcheck="false">// Define Error Codes let statusCode = { OK: 200, FOUR_ZERO_FOUR: 404, FOUR_ZERO_THREE: 403, FOUR_ZERO_ONE: 401, FIVE_ZERO_ZERO: 500 };   // Define Error Messages let statusMessage = { SERVER_BUSY : 'Our Servers are busy. Please try again later.', DATA_UPDATED: 'Data updated successfully.', DELETE_DATA : 'Delete data successfully',   };   module.exports = { statusCode: statusCode, statusMessage: statusMessage } </pre>

Now the next part is model, So create a folder named Models and create a file Article.js and add the below code in it:

<pre class="ql-syntax" spellcheck="false">let mysqlConfig = require("../Utilities/mysqlConfig");   let initialize = () => { mysqlConfig.getDB().query("create table IF NOT EXISTS article (id INT auto_increment primary key, category VARCHAR(30), title VARCHAR(24))");   }   module.exports = { initialize: initialize } </pre>

Now create DAO folder and add a file articleDAO.js for writting the mysql queries common functions:

<pre class="ql-syntax" spellcheck="false">let dbConfig = require("../Utilities/mysqlConfig");

 
let getArticle = (criteria, callback) => {
//criteria.aricle_id ? conditions += and aricle_id = '${criteria.aricle_id}' : true;
dbConfig.getDB().query(select * from article where 1,criteria, callback);
}
 
let getArticleDetail = (criteria, callback) => {
    let conditions = "";
criteria.id ? conditions +=  and id = '${criteria.id}' : true;
dbConfig.getDB().query(select * from article where 1&nbsp;${conditions}, callback);
}
 
let createArticle = (dataToSet, callback) => {
console.log("insert into article set ? ", dataToSet,'pankaj')
dbConfig.getDB().query("insert into article set ? ", dataToSet, callback);
}
 
let deleteArticle = (criteria, callback) => {
let conditions = "";
criteria.id ? conditions +=  and id = '${criteria.id}' : true;
console.log(delete from article where 1&nbsp;${conditions});
dbConfig.getDB().query(delete from article where 1&nbsp;${conditions}, callback);
 
}
 
let updateArticle = (criteria,dataToSet,callback) => {
    let conditions = "";
let setData = "";
criteria.id ? conditions +=  and id = '${criteria.id}' : true;
dataToSet.category ? setData += category = '${dataToSet.category}' : true;
dataToSet.title ? setData += , title = '${dataToSet.title}' : true;
console.log(UPDATE article SET&nbsp;${setData}&nbsp;where 1&nbsp;${conditions});
dbConfig.getDB().query(UPDATE article SET&nbsp;${setData}&nbsp;where 1&nbsp;${conditions}, callback);
}
module.exports = {
getArticle : getArticle,
createArticle : createArticle,
deleteArticle : deleteArticle,
updateArticle : updateArticle,
getArticleDetail : getArticleDetail
}
</pre>

Now one create Services folder and add a file article.js for all the logic of API

<pre class="ql-syntax" spellcheck="false"> let async = require('async'),
parseString = require('xml2js').parseString;
 
let util = require('../Utilities/util'),
articleDAO = require('../DAO/articleDAO');
//config = require("../Utilities/config").config;
 
 
/**API to create the atricle */
let createArticle = (data, callback) => {
async.auto({
article: (cb) => {
var dataToSet = {
"category":data.category?data.category:'',
"title":data.title,
}
console.log(dataToSet);
articleDAO.createArticle(dataToSet, (err, dbData) => {
if (err) {
cb(null, { "statusCode": util.statusCode.FOUR_ZERO_ONE, "statusMessage":util.statusMessage.SERVER_BUSY });
return;
}
 
cb(null, { "statusCode": util.statusCode.OK, "statusMessage":util.statusMessage.DATA_UPDATED,"result":dataToSet });
});
}
//]
}, (err, response) => {
callback(response.article);
});
}
 
/**API to update the article */
let updateArticle = (data,callback) => {
async.auto({
articleUpdate :(cb) =>{
if (!data.id) {
cb(null, { "statusCode": util.statusCode.FOUR_ZERO_ONE, "statusMessage":util.statusMessage.PARAMS_MISSING })
return;
}
console.log('phase 1');
var criteria = {
id : data.id,
}
var dataToSet={
"category": data.category,
"title":data.title,
}
console.log(criteria,'test',dataToSet);
                    articleDAO.updateArticle(criteria, dataToSet, (err, dbData)=>{
                        if(err){
cb(null,{"statusCode":util.statusCode.FOUR_ZERO_ONE,"statusMessage":util.statusMessage.SERVER_BUSY});
                        return; 
                        }
                        else{
cb(null, { "statusCode": util.statusCode.OK, "statusMessage":util.statusMessage.DATA_UPDATED,"result":dataToSet });                        
                        }
                    });
}
}, (err,response) => {
callback(response.articleUpdate);
});
}
 
/**API to delete the subject */
let deleteArticle = (data,callback) => {
console.log(data,'data to set')
async.auto({
removeArticle :(cb) =>{
if (!data.id) {
cb(null, { "statusCode": util.statusCode.FOUR_ZERO_ONE, "statusMessage":util.statusMessage.PARAMS_MISSING })
return;
}
var criteria = {
id : data.id,
}
articleDAO.deleteArticle(criteria,(err,dbData) => {
if (err) {
console.log(err);
cb(null, { "statusCode": util.statusCode.FOUR_ZERO_ONE, "statusMessage":util.statusMessage.SERVER_BUSY });
return;
}
cb(null, { "statusCode": util.statusCode.OK, "statusMessage": util.statusMessage.DELETE_DATA });
});
}
}, (err,response) => {
callback(response.removeArticle);
});
}
 
/***API to get the article list */
let getArticle = (data, callback) => {
async.auto({
article: (cb) => {
articleDAO.getArticle({},(err, data) => {
if (err) {
cb(null, {"errorCode": util.statusCode.INTERNAL_SERVER_ERROR,"statusMessage":util.statusMessage.SERVER_BUSY});
return;
}
cb(null, data);
return;
});
}
}, (err, response) => {
callback(response.article);
})
}
 
/***API to get the article detail by id */
let getArticleById = (data, callback) => {
async.auto({
article: (cb) => {
let criteria = {
"id":data.id
}
articleDAO.getArticleDetail(criteria,(err, data) => {
if (err) {
console.log(err,'error----');
cb(null, {"errorCode": util.statusCode.INTERNAL_SERVER_ERROR,"statusMessage":util.statusMessage.SERVER_BUSY});
return;
}
cb(null, data[0]);
return;
});
}
}, (err, response) => {
callback(response.article);
})
}
 
module.exports = {
createArticle : createArticle,
updateArticle : updateArticle,
deleteArticle : deleteArticle,
getArticle : getArticle,
getArticleById : getArticleById
};
</pre>

3. Create angular component for performing CRUD task of article

<pre class="ql-syntax" spellcheck="false">ng g component article
</pre>

Above command will generate all required files for build article component and also automatically added this component to app.module.ts.

<pre class="ql-syntax" spellcheck="false">create src/app/article/article.component.css (0 bytes)
create src/app/article/article.component.html (23 bytes)
create src/app/article/article.component.spec.ts (614 bytes)
create src/app/article/article.component.ts (321 bytes)
update src/app/app.module.ts (390 bytes)
</pre>

Now we need to add HttpClientModule to app.module.ts. Open and edit src/app/app.module.ts then add this import. And add it to @NgModule imports after BrowserModule. Now our app.module.ts will have following code:

<pre class="ql-syntax" spellcheck="false"> import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { ReactiveFormsModule } from '@angular/forms';
import { HttpModule } from '@angular/http';
 
import { AppComponent } from './app.component';
import { ArticleComponent } from './article.component';
import { ArticleService } from './article.service';
 
@NgModule({
imports: [
BrowserModule,
HttpModule,
ReactiveFormsModule
],
declarations: [
AppComponent,
ArticleComponent
],
providers: [
ArticleService
],
bootstrap: [
AppComponent
]
})
export class AppModule { }
</pre>

Now create a service file where we will make all the request to the server for CRUD operation. Command for creating service is ng g service artcle , for now I have just created a file named it article.service.ts. Let's have a look in the code inside this file.

<pre class="ql-syntax" spellcheck="false">import { Injectable } from '@angular/core';
import { Http, Response, Headers, URLSearchParams, RequestOptions } from '@angular/http';
import { Observable } from 'rxjs';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/catch';
 
import { Article } from './article';
 
@Injectable()
export class ArticleService {
//URL for CRUD operations
    articleUrl = "http://localhost:3000/article";
    //Create constructor to get Http instance
    constructor(private http:Http) {
    }
    
    //Fetch all articles
getAllArticles(): Observable<Article[]> {
return this.http.get(this.articleUrl+"/get-article")
              .map(this.extractData)
         .catch(this.handleError);
 
}
    //Create article
createArticle(article: Article):Observable<number> {
     let cpHeaders = new Headers({ 'Content-Type': 'application/json' });
let options = new RequestOptions({ headers: cpHeaders });
return this.http.post(this.articleUrl+"/create-article", article, options)
.map(success => success.status)
.catch(this.handleError);
}
    //Fetch article by id
getArticleById(articleId: string): Observable<Article> {
        let cpHeaders = new Headers({ 'Content-Type': 'application/json' });
        let options = new RequestOptions({ headers: cpHeaders });
        console.log(this.articleUrl +"/get-article-by-id?id="+ articleId);
        return this.http.get(this.articleUrl +"/get-article-by-id?id="+ articleId)
             .map(this.extractData)
             .catch(this.handleError);
}   
    //Update article
updateArticle(article: Article):Observable<number> {
     let cpHeaders = new Headers({ 'Content-Type': 'application/json' });
        let options = new RequestOptions({ headers: cpHeaders });
return this.http.put(this.articleUrl +"/update-article", article, options)
.map(success => success.status)
.catch(this.handleError);
}
//Delete article    
deleteArticleById(articleId: string): Observable<number> {
        let cpHeaders = new Headers({ 'Content-Type': 'application/json' });
        let options = new RequestOptions({ headers: cpHeaders });
        return this.http.delete(this.articleUrl +"/delete-article?id="+ articleId)
             .map(success => success.status)
             .catch(this.handleError);
}   
    private extractData(res: Response) {
        let body = res.json();
return body;
}
private handleError (error: Response | any) {
        console.error(error.message || error);
        return Observable.throw(error.status);
}
}
</pre>

In the above file we have made all the http request for the CRUD operation. Observables of rxjs library has been used to handle the data fetching from http request.

 

Now let's move to the next file, article.component.ts. Here we have all the login part of the app. Let's have a look code inside this file:

<pre class="ql-syntax" spellcheck="false">import { Component, OnInit } from '@angular/core';
import { FormControl, FormGroup, Validators } from '@angular/forms';
 
import { ArticleService } from './article.service';
import { Article } from './article';
 
@Component({
selector: 'app-article',
templateUrl: './article.component.html',
styleUrls: ['./article.component.css']
})
export class ArticleComponent implements OnInit {
//Component properties
allArticles: Article[];
statusCode: number;
requestProcessing = false;
articleIdToUpdate = null;
processValidation = false;
//Create form
articleForm = new FormGroup({
title: new FormControl('', Validators.required),
category: new FormControl('', Validators.required)   
});
//Create constructor to get service instance
constructor(private articleService: ArticleService) {
}
//Create ngOnInit() and and load articles
ngOnInit(): void {
     this.getAllArticles();
}
//Fetch all articles
 
getAllArticles() {
        this.articleService.getAllArticles()
         .subscribe(
data => this.allArticles = data,
                errorCode => this.statusCode = errorCode);
                
}
//Handle create and update article
onArticleFormSubmit() {
     this.processValidation = true;
     if (this.articleForm.invalid) {
     return; //Validation failed, exit from method.
     }
     //Form is valid, now perform create or update
this.preProcessConfigurations();
     let article = this.articleForm.value;
     if (this.articleIdToUpdate === null) {
     //Generate article id then create article
this.articleService.getAllArticles()
     .subscribe(articles => {
            
         //Generate article id    
         let maxIndex = articles.length - 1;
         let articleWithMaxIndex = articles[maxIndex];
         let articleId = articleWithMaxIndex.id + 1;
         article.id = articleId;
         console.log(article,'this is form data---');
         //Create article
    this.articleService.createArticle(article)
             .subscribe(successCode => {
                    this.statusCode = successCode;
                    this.getAllArticles();  
                    this.backToCreateArticle();
                 },
                 errorCode => this.statusCode = errorCode
             );
         });        
     } else {
  //Handle update article
article.id = this.articleIdToUpdate;        
     this.articleService.updateArticle(article)
     .subscribe(successCode => {
         this.statusCode = successCode;
                 this.getAllArticles();  
                    this.backToCreateArticle();
             },
         errorCode => this.statusCode = errorCode);  
     }
}
//Load article by id to edit
loadArticleToEdit(articleId: string) {
this.preProcessConfigurations();
this.articleService.getArticleById(articleId)
     .subscribe(article => {
            console.log(article,'poiuytre');
         this.articleIdToUpdate = article.id;
                    this.articleForm.setValue({ title: article.title, category: article.category });
                    this.processValidation = true;
                    this.requestProcessing = false;
         },
         errorCode => this.statusCode = errorCode);
}
//Delete article
deleteArticle(articleId: string) {
this.preProcessConfigurations();
this.articleService.deleteArticleById(articleId)
     .subscribe(successCode => {
         //this.statusCode = successCode;
                    //Expecting success code 204 from server
                    this.statusCode = 204;
                 this.getAllArticles();  
                 this.backToCreateArticle();
             },
         errorCode => this.statusCode = errorCode);
}
//Perform preliminary processing configurations
preProcessConfigurations() {
this.statusCode = null;
     this.requestProcessing = true;
}
//Go back from update to create
backToCreateArticle() {
this.articleIdToUpdate = null;
this.articleForm.reset(); 
     this.processValidation = false;
}
}
</pre>

Now we have to show the task over browser, So lets have a look inside article.component.html file.

<pre class="ql-syntax" spellcheck="false"><h1 class="text-center">Angular 7 CRUD Demo App</h1>
<h3 class="text-center" *ngIf="articleIdToUpdate; else create">
Update Article for Id: {{articleIdToUpdate}}
</h3>
<ng-template #create>
<h3 class="text-center"> Create New Article </h3>
</ng-template>
<div>
<form [formGroup]="articleForm" (ngSubmit)="onArticleFormSubmit()">
<table class="table-striped" style="margin:0 auto;">
<tr><td>Enter Title</td><td><input formControlName="title">
   <label *ngIf="articleForm.get('title').invalid && processValidation" [ngClass] = "'error'"> Title is required. </label>
 </td></tr>
<tr><td>Enter Category</td><td><input formControlName="category">
   <label *ngIf="articleForm.get('category').invalid && processValidation" [ngClass] = "'error'">Category is required. </label>
  </td></tr>  
<tr><td colspan="2">
   <button class="btn btn-default" *ngIf="!articleIdToUpdate">CREATE</button>
    <button class="btn btn-default" *ngIf="articleIdToUpdate">UPDATE</button>
   <button (click)="backToCreateArticle()" *ngIf="articleIdToUpdate">Go Back</button>
  </td></tr>
</table>
</form>
<br/>
<div class="text-center" *ngIf="statusCode; else processing">
<div *ngIf="statusCode === 201" [ngClass] = "'success'">
   Article added successfully.
</div>
<div *ngIf="statusCode === 409" [ngClass] = "'success'">
Article already exists.
</div>   
<div *ngIf="statusCode === 200" [ngClass] = "'success'">
Article updated successfully.
</div>   
<div *ngIf="statusCode === 204" [ngClass] = "'success'">
Article deleted successfully.
</div>   
<div *ngIf="statusCode === 500" [ngClass] = "'error'">
Internal Server Error.
</div> 
</div>
<ng-template #processing>
  <img *ngIf="requestProcessing" src="assets/images/loading.gif">
</ng-template>
</div>
<h3 class="text-center">Article List</h3>
<table class="table-striped" style="margin:0 auto;" *ngIf="allArticles">
<tr><th> Id</th> <th>Title</th><th>Category</th><th></th><th></th></tr>
<tr *ngFor="let article of allArticles" >
<td>{{article.id}}</td> <td>{{article.title}}</td> <td>{{article.category}}</td>
  <td><button class="btn btn-default" type="button"(click)="loadArticleToEdit(article.id)">Edit</button> </td>
  <td><button class="btn btn-default" type="button"(click)="deleteArticle(article.id)">Delete</button></td>
</tr>
</table>
</pre>

Now since I have created server and client two separate folder for nodejs and angular task. So will run both the apps with npm start over two tabs of terminal.

On the browser, over link <a href="http://localhost:4200." target="_blank">http://localhost:4200.</a> App will look like below

 

That’s all for now. Thank you for reading and I hope this post will be very helpful for creating CRUD operations with angular7,node.js & mysql.



================================================

Thanks for reading :heart: If you liked this post, share it with all of your programming buddies! Follow me on <a href="https://l.morioh.com/b0a3f595aa?r=https://www.facebook.com/angular4u" title="" target="_blank">Facebook</a> | <a href="https://l.morioh.com/b0a3f595aa?r=https://twitter.com/codek_tv" title="" target="_blank">Twitter</a>

Learn More

☞ <a href="https://l.morioh.com/b0a3f595aa?r=http://learnstartup.net/p/H1jE_tD3l" title="" target="_blank">Angular 8 (formerly Angular 2) - The Complete Guide</a>

☞ <a href="https://l.morioh.com/b0a3f595aa?r=http://learnstartup.net/p/HJigQzgZx" title="" target="_blank">Learn and Understand AngularJS</a>

☞ <a href="https://l.morioh.com/b0a3f595aa?r=http://learnstartup.net/p/ry7Ey9yKW" title="" target="_blank">The Complete Angular Course: Beginner to Advanced</a>

☞ <a href="https://l.morioh.com/b0a3f595aa?r=http://learnstartup.net/p/rkjpGfa5W" title="" target="_blank">Angular Crash Course for Busy Developers</a>

☞ <a href="https://l.morioh.com/b0a3f595aa?r=http://learnstartup.net/p/SkdU19JFZ" title="" target="_blank">Angular Essentials (Angular 2+ with TypeScript)</a>

☞ <a href="https://l.morioh.com/b0a3f595aa?r=http://learnstartup.net/p/B1oHaY8cM" title="" target="_blank">Angular (Full App) with Angular Material, Angularfire & NgRx</a>

☞ <a href="https://l.morioh.com/b0a3f595aa?r=http://learnstartup.net/p/Skf7ILFw3l" title="" target="_blank">Angular & NodeJS - The MEAN Stack Guide</a>



The perfect architecture flow for your next Node.js project

The perfect architecture flow for your next Node.js project

A good start is half the battle, said someone wiser than me. And I can’t think of any quote that would better describe the situation every developer gets into whenever starting a new project. Laying out a project’s structure in a practical way is one of the hardest points of the development process and, indeed, a delicate one.

We can define a path about discussing Node.js technologies, how to choose what front-end framework to use, and now we can try to dig deeper on how to structure our web apps once we have decided on the tech stack to use.

The importance of good architecture

Having a good starting point when it comes to our project architecture is vital for the life of the project itself and how you will be able to tackle changing needs in the future. A bad, messy project architecture often leads to:

  • Unreadable and messy code, making the development process longer and the product itself harder to test

  • Useless repetition, making code harder to maintain and manage

  • Difficulty implementing new features. Since the structure can become a total mess, adding a new feature without messing up existing code can become a real problem

With these points in mind, we can all agree that our project architecture is extremely important, and we can also declare a few points that can help us determine what this architecture must help us do:

  • Achieve clean and readable code

  • Achieve reusable pieces of code across our application

  • Help us to avoid repetitions

  • Make life easier when adding a new feature into our application

Establishing a flow

Now we can discuss what I usually refer to as the application structure flow. The application structure flow is a set of rules and common practices to adopt while developing our applications. These are the results of years of experience working with a technology and understanding what works properly and what doesn’t.

The goal of this article is to create a quick reference guide to establishing the perfect flow structure when developing Node.js applications. Let’s start to define our rules:

Rule #1: Correctly organize our files into folders

Everything has to have its place in our application, and a folder is the perfect place to group common elements. In particular, we want to define a very important separation, which brings us to rule number #2:

Rule #2: Keep a clear separation between the business logic and the API routes

See, frameworks like Express.js are amazing. They provide us with incredible features for managing requests, views, and routes. With such support, it might be tempting for us to put our business logic into our API routes. But this will quickly make them into giant, monolithic blocks that will reveal themselves to be unmanageable, hard to read, and prone to decomposition.

Please also don’t forget about how the testability of our application will decrease, with consequently longer development times. At this point, you might be wondering, “How do we solve this problem, then? Where can I put my business logic in a clear and intelligent way?” The answer is revealed in rule number #3.

Rule #3: Use a service layer

This is the place where all our business logic should live. It’s basically a collection of classes, each with its methods, that will be implementing our app’s core logic. The only part you should ignore in this layer is the one that accesses the database; that should be managed by the data access layer.

Now that we have defined these three initial rules, we can graphically represent the result like this:
This is image title
Separating our business logic from our API routes.

And the subsequent folder structure sending us back to rule #1 can then become:
This is image title

By looking at this last image, we can also establish two other rules when thinking about our structure.

Rule #4: Use a config folder for configuration files

This is image title

Rule #5: Have a scripts folder for long npm scripts

This is image title

Rule #6: Use dependency injection

Node.js is literally packed with amazing features and tools to make our lives easier. However, as we know, working with dependencies can be quite troublesome most of the time due to problems that can arise with testability and code manageability.

There is a solution for that, and it’s called dependency injection.

Dependency injection is a software design pattern in which one or more dependencies (or services) are injected, or passed by reference, into a dependent object.

By using this inside our Node applications, we:

  • Have an easier unit testing process, passing dependencies directly to the modules we would like to use instead of hardcoding them

  • Avoid useless modules coupling, making maintenance much easier

  • Provide a faster git flow. After we defined our interfaces, they will stay like that, so we can avoid any merge conflicts.
    This is image title
    Using Node.js without dependency injection.

Simple but still not very flexible as an approach to our code. What happens if we want to alter this test to use an example database? We should alter our code to adapt it to this new need. Why not pass the database directly as a dependency instead?
This is image title

Rule #7: Use unit testing

Now that we know we have got dependency injection under our belt, we can also implement unit testing for our project. Testing is an incredibly important stage in developing our applications. The whole flow of the project — not just the final result — depends on it since buggy code would slow down the development process and cause other problems.

A common way to test our applications is to test them by units, the goal of which is to isolate a section of code and verify its correctness. When it comes to procedural programming, a unit may be an individual function or procedure. This process is usually performed by the developers who write the code.

Benefits of this approach include:

Improved code quality

Unit testing improves the quality of your code, helping you to identify problems you might have missed before the code goes on to other stages of development. It will expose the edge cases and makes you write better overall code

Bugs are found earlier

Issues here are found at a very early stage. Since the tests are going to be performed by the developer who wrote the code, bugs will be found earlier, and you will be able to avoid the extremely time-consuming process of debugging

Cost reduction

Fewer flaws in the application means less time spent debugging it, and less time spent debugging it means less money spent on the project. Time here is an especially critical factor since this precious unit can now be allocated to develop new features for our product

Rule #8: Use another layer for third-party services calls

Often, in our application, we may want to call a third-party service to retrieve certain data or perform some operations. And still, very often, if we don’t separate this call into another specific layer, we might run into an out-of-control piece of code that has become too big to manage.

A common way to solve this problem is to use the pub/sub pattern. This mechanism is a messaging pattern where we have entities sending messages called publishers, and entities receiving them called subscribers.

Publishers won’t program the messages to be sent directly to specific receivers. Instead, they will categorize published messages into specific classes without knowledge of which subscribers, if any, may be dealing with them.

In a similar way, the subscribers will express interest in dealing with one or more classes and only receive messages that are of interest to them — all without knowledge of which publishers are out there.

The publish-subscribe model enables event-driven architectures and asynchronous parallel processing while improving performance, reliability, and scalability.

Rule #9: Use a linter

This simple tool will help you to perform a faster and overall better development process, helping you to keep an eye on small errors while keeping the entire application code uniform.
This is image title
Example of using a linter.

Rule #10: Use a style guide

Still thinking about how to properly format your code in a consistent way? Why not adapt one of the amazing style guides that Google or Airbnb have provided to us? Reading code will become incredibly easier, and you won’t get frustrated trying to understand how to correctly position that curly brace.
This is image title
Google’s JavaScript style guide.

Rule #11: Always comment your code

Writing a difficult piece of code where it’s difficult to understand what you are doing and, most of all, why? Never forget to comment it. This will become extremely useful for your fellow developers and to your future self, all of whom will be wondering why exactly you did something six months after you first wrote it.

Rule #12: Keep an eye on your file sizes

Files that are too long are extremely hard to manage and maintain. Always keep an eye on your file length, and if they become too long, try to split them into modules packed in a folder as files that are related together.

Rule #13: Always use gzip compression

The server can use gzip compression to reduce file sizes before sending them to a web browser. This will reduce latency and lag.
This is image title
An example of using gzip compression with Express.

Rule #14: Use promises

Using callbacks is the simplest possible mechanism for handling your asynchronous code in JavaScript. However, raw callbacks often sacrifice the application control flow, error handling, and semantics that were so familiar to us when using synchronous code. A solution for that is using promises in Node.js.

Promises bring in more pros than cons by making our code easier to read and test while still providing functional programming semantics together with a better error-handling platform.
This is image title
A basic example of a promise.

Rule #15: Use promises’ error handling support

Finding yourself in a situation where you have an unexpected error or behavior in your app is not at all pleasant, I can guarantee. Errors are impossible to avoid when writing our code. That’s simply part of being human.

Dealing with them is our responsibility, and we should always not only use promises in our applications, but also make use of their error handling support provided by the catch keyword.
This is image title

Conclusion

Creating a Node.js application can be challenging, I hope this set of rules helped you to put yourself in the right direction when establishing what type of architecture you are going to use, and what practices are going to support that architecture.

Originally published by Piero Borrelli at blog.logrocket.com

Build a CMS with Laravel and Vue

Build a CMS with Laravel and Vue

Build a CMS with Laravel and Vue - Part 1: Setting up

The birth of the internet has since redefined content accessibility for the better, causing a distinct rise in content consumption across the globe. The average user of the internet consumes and produces some form of content formally or informally.

An example of an effort at formal content creation is when an someone makes a blog post about their work so that a targeted demographic can easily find their website. This type of content is usually served and managed by a CMS (Content Management System). Some popular ones are WordPress, Drupal, and SilverStripe.

A CMS helps content creators produce content in an easily consumable format. In this tutorial series, we will consider how to build a simple CMS from scratch using Laravel and Vue.

Our CMS will be able to make new posts, update existing posts, delete posts that we do not need anymore, and also allow users make comments to posts which will be updated in realtime using Pusher. We will also be able to add featured images to posts to give them some visual appeal.

When we are done, we will be able to have a CMS that looks like this:


Prerequisites

To follow along with this series, a few things are required:

  • Basic knowledge of PHP.
  • Basic knowledge of the Laravel framework.
  • Basic knowledge of JavaScript (ES6 syntax).
  • Basic knowledge of Vue.
  • Postman installed on your machine.
The source code for this project is available here on GitHub.

Installing the Laravel CLI

If you already have the Laravel CLI installed on your machine, please skip this section.

The first thing we need to do is install the Laravel CLI, and the Laravel dependencies. The CLI will be instrumental in creating new Laravel projects whenever we need to create one. Laravel requires PHP and a few other tools and extensions, so we need to first install these first before installing the CLI.

Here’s a list of the dependencies as documented on the official Laravel documentation:

Let’s install them one at a time.


Installing PHP

An equivalent for Windows users could be to download and install XAMPP here. XAMPP comes with a UI for installing most of the other things you have to install manually below. Hence, Windows users may skip the next few steps until the Installing Composer sub-heading.

Open a fresh instance of the terminal and paste the following command:

<pre class="ql-syntax" spellcheck="false"> # Linux Users $ sudo apt-get install php7.2
# Mac users
$ brew install php72

</pre>

As at the time of writing this article, PHP 7.2 is the latest stable version of PHP so the command above installs it on your machine.

On completion, you can check that PHP has been installed to your machine with the following command:

<pre class="ql-syntax" spellcheck="false"> $ php -v

</pre>

Installing the Mbstring extension

To install the mbstring extension for PHP, paste the following command in the open terminal:

<pre class="ql-syntax" spellcheck="false"> # Linux users
$ sudo apt-get install php7.2-mbstring

# Mac users
# You don't have to do anything as it is installed automatically.

</pre>

To check if the mbstring extension has been installed successfully, you can run the command below:

<pre class="ql-syntax" spellcheck="false"> $ php -m | grep mbstring

</pre>

Installing the XML PHP extension

To install the XML extension for PHP, paste the following command in the open terminal:

<pre class="ql-syntax" spellcheck="false"> # Linux users
$ sudo apt-get install php-xml

# Mac users
# You don't have to do anything as it is installed automatically.

</pre>

To check if the xml extension has been installed successfully, you can run the command below:

<pre class="ql-syntax" spellcheck="false"> $ php -m | grep xml

</pre>

Installing the ZIP PHP extension

To install the zip extension for PHP, paste the following command in your terminal:

<pre class="ql-syntax" spellcheck="false"> # Linux users
$ sudo apt-get install php7.2-zip

# Mac users
# You don't have to do anything as it is installed automatically.

</pre>

To check if the zip extension has been installed successfully, you can run the command below:

<pre class="ql-syntax" spellcheck="false"> $ php -m | grep zip

</pre>

Installing curl

Windows users may need to download curl from here.

To install curl, paste the following command in your terminal:

<pre class="ql-syntax" spellcheck="false"> # Linux users
$ sudo apt-get install curl

# Mac users using Homebrew (https://brew.sh)
$ brew install curl

</pre>

To verify that curl has been installed successfully, run the following command:

<pre class="ql-syntax" spellcheck="false"> $ curl --version

</pre>

Installing Composer

Windows users can download and install Composer here. After the installation is complete, start a fresh instance of the command prompt as administrator and run this command anytime you need composer:
php composer.phar

Now that we have curl installed on our machine, let’s pull in Composer with this command:

<pre class="ql-syntax" spellcheck="false"> $ curl -sS https://getcomposer.org/installer | sudo php -- --install-dir=/usr/local/bin --filename=composer

</pre>

For us to run Composer in the future without calling sudo, we may need to change the permission, however you should only do this if you have problems installing packages:

<pre class="ql-syntax" spellcheck="false"> $ sudo chown -R $USER ~/.composer/

</pre>

Installing the Laravel installer

At this point, we can already create a new Laravel project using Composer’s create-project command, which looks like this:

<pre class="ql-syntax" spellcheck="false"> $ composer create-project --prefer-dist laravel/laravel project-name

</pre>

But we will go one step further and install the Laravel installer using composer:

<pre class="ql-syntax" spellcheck="false"> $ composer global require "laravel/installer"

</pre>

If you are on Windows, you may need to run the previous command in an advanced terminal such as PowerShell or the Gitbash terminal. Windows users can also skip the steps below.

After the installation, we will need to add the PATH to the bashrc file so that our terminal can recognize the laravel command:

<pre class="ql-syntax" spellcheck="false"> $ echo 'export PATH="$HOME/.composer/vendor/bin:$PATH"' >> ~/.bashrc
$ source ~/.bashrc

</pre>

Creating the CMS project

Now that we have the official Laravel CLI installed on our machine, let’s create our CMS project using the installer. In your terminal window, cd to the project directory you want to create the project in and run the following command:

<pre class="ql-syntax" spellcheck="false"> $ laravel new cms

</pre>

At the time of writing this article, the latest version of Laravel is 5.6

We will navigate into the project directory and serve the application using PHP’s web server:

<pre class="ql-syntax" spellcheck="false"> $ cd cms
$ php artisan serve

</pre>

Now, when we visit http://127.0.0.1:8000/, we will see the default Laravel template:


Setting up the database

In this series, we will be using MySQL as our database system so a prerequisite for this section is that you have MySQL installed on your machine.

You can follow the steps below to install and configure MySQL:

  • Linux users - check here for a detailed guide.
  • Mac users, you can just run the command brew install mysql.
  • Windows users who installed XAMPP, as suggested earlier, do not need to install MySQL as it comes preinstalled.

You will also need a special driver that makes it possible for PHP to work with MySQL, you can install it with this command:

<pre class="ql-syntax" spellcheck="false"> # Linux users
$ sudo apt-get install php7.2-mysql

# Mac Users
# You don't have to do anything as it is installed automatically.

</pre>

Load the project directory in your favorite text editor and there should be a .env file in the root of the folder. This is where Laravel stores its environment variables.

Create a new MySQL database and call it laravelcms. In the .env file, update the database configuration keys as seen below:

<pre class="ql-syntax" spellcheck="false"> DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=laravelcms
DB_USERNAME=YourUsername
DB_PASSWORD=YourPassword

</pre>

Replace the DB_USERNAME and DB_PASSWORD with your MySQL database credentials.

Setting up user roles

Like most content management systems, we are going to have a user role system so that our blog can have multiple types of users; the admin and regular user. The admin should be able to create a post and perform other CRUD operations on a post. The regular user, on the other hand, should be able to view and comment on a post.

For us to implement this functionality, we need to implement user authentication and add a simple role authorization system.


Setting up user authentication

Laravel provides user authentication out of the box, which is great, and we can key into the feature by running a single command:

<pre class="ql-syntax" spellcheck="false"> $ php artisan make:auth

</pre>

The above will create all that’s necessary for authentication in our application so we do not need to do anything extra.


Setting up role authorization

We need a model for the user roles so let’s create one and an associated migration file:

<pre class="ql-syntax" spellcheck="false"> $ php artisan make:model Role -m

</pre>

In the database/migrations folder, find the newly created migration file and update the CreateRolesTable class with this snippet:

<pre class="ql-syntax" spellcheck="false"> <?php // File: ./database/migrations/*_create_roles_table.php

// [...]

class CreateRolesTable extends Migration
{
    public function up()
    {
        Schema::create('roles', function (Blueprint $table) {
            $table-&gt;increments('id');
            $table-&gt;string('name');
            $table-&gt;string('description');
            $table-&gt;timestamps();
        });
    }

    public function down()
    {
        Schema::dropIfExists('roles');
    }
}

</pre>

We intend to create a many-to-many relationship between the User and Role models so let’s add a relationship method on both models.

Open the User model and add the following method:

<pre class="ql-syntax" spellcheck="false"> // File: ./app/User.php
public function roles()
{
return $this->belongsToMany(Role::class);
}
</pre>

Open the Role model and include the following method:

<pre class="ql-syntax" spellcheck="false"> // File: ./app/Role.php
public function users()
{
return $this->belongsToMany(User::class);
}
</pre>

We are also going to need a pivot table to associate each user with a matching role so let’s create a new migration file for the role_user table:

<pre class="ql-syntax" spellcheck="false"> $ php artisan make:migration create_role_user_table

</pre>

In the database/migrations folder, find the newly created migration file and update the CreateRoleUserTable class with this snippet:

<pre class="ql-syntax" spellcheck="false"> // File: ./database/migrations/*_create_role_user_table.php
<?php

// [...]

class CreateRoleUserTable extends Migration
{

    public function up()
    {
        Schema::create('role_user', function (Blueprint $table) {
            $table-&gt;increments('id');
            $table-&gt;integer('role_id')-&gt;unsigned();
            $table-&gt;integer('user_id')-&gt;unsigned();
        });
    }

    public function down()
    {
        Schema::dropIfExists('role_user');
    }
}

</pre>

Next, let’s create seeders that will populate the users and roles tables with some data. In your terminal, run the following command to create the database seeders:

<pre class="ql-syntax" spellcheck="false"> $ php artisan make:seeder RoleTableSeeder
$ php artisan make:seeder UserTableSeeder

</pre>

In the database/seeds folder, open the RoleTableSeeder.php file and replace the contents with the following code:

<pre class="ql-syntax" spellcheck="false"> // File: ./database/seeds/RoleTableSeeder.php
<?php

use App\Role;
use Illuminate\Database\Seeder;

class RoleTableSeeder extends Seeder
{
    public function run()
    {
        $role_regular_user = new Role;
        $role_regular_user-&gt;name = 'user';
        $role_regular_user-&gt;description = 'A regular user';
        $role_regular_user-&gt;save();

        $role_admin_user = new Role;
        $role_admin_user-&gt;name = 'admin';
        $role_admin_user-&gt;description = 'An admin user';
        $role_admin_user-&gt;save();
    }
}

</pre>

Open the UserTableSeeder.php file and replace the contents with the following code:

<pre class="ql-syntax" spellcheck="false"> // File: ./database/seeds/UserTableSeeder.php
<?php

use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\Hash;
use App\User;
use App\Role;

class UserTableSeeder extends Seeder
{

    public function run()
    {
        $user = new User;
        $user-&gt;name = 'Samuel Jackson';
        $user-&gt;email = '[email protected]';
        $user-&gt;password = bcrypt('samuel1234');
        $user-&gt;save();
        $user-&gt;roles()-&gt;attach(Role::where('name', 'user')-&gt;first());

        $admin = new User;
        $admin-&gt;name = 'Neo Ighodaro';
        $admin-&gt;email = '[email protected]';
        $admin-&gt;password = bcrypt('neo1234');
        $admin-&gt;save();
        $admin-&gt;roles()-&gt;attach(Role::where('name', 'admin')-&gt;first());
    }
}

</pre>

We also need to update the DatabaseSeeder class. Open the file and update the run method as seen below:

<pre class="ql-syntax" spellcheck="false"> // File: ./database/seeds/DatabaseSeeder.php
<?php

// [...]

class DatabaseSeeder extends Seeder
{
    public function run()
    {
        $this-&gt;call([
            RoleTableSeeder::class, 
            UserTableSeeder::class,
        ]);
    }
}

</pre>

Next, let’s update the User model. We will be adding a checkRoles method that checks what role a user has. We will return a 404 page where a user doesn’t have the expected role for a page. Open the User model and add these methods:

<pre class="ql-syntax" spellcheck="false"> // File: ./app/User.php
public function checkRoles($roles)
{
if ( ! is_array($roles)) {
$roles = [$roles];
}

    if ( ! $this-&gt;hasAnyRole($roles)) {
        auth()-&gt;logout();
        abort(404);
    }
}

public function hasAnyRole($roles): bool
{
    return (bool) $this-&gt;roles()-&gt;whereIn('name', $roles)-&gt;first();
}

public function hasRole($role): bool
{
    return (bool) $this-&gt;roles()-&gt;where('name', $role)-&gt;first();
}

</pre>

Let’s modify the RegisterController.php file in the Controllers/Auth folder so that a default role, the user role, is always attached to a new user at registration.

Open the RegisterController and update the create action with the following code:

<pre class="ql-syntax" spellcheck="false"> // File: ./app/Http/Controllers/Auth/RegisterController.php
protected function create(array $data)
{
$user = User::create([
'name' => $data['name'],
'email' => $data['email'],
'password' => bcrypt($data['password']),
]);

    $user-&gt;roles()-&gt;attach(\App\Role::where('name', 'user')-&gt;first());

    return $user;
}

</pre>

Now let’s migrate and seed the database so that we can log in with the sample accounts. To do this, run the following command in your terminal:

<pre class="ql-syntax" spellcheck="false"> $ php artisan migrate:fresh --seed

</pre>

In order to test that our roles work as they should, we will make an update to the HomeController.php file. Open the HomeController and update the index method as seen below:

<pre class="ql-syntax" spellcheck="false"> // File: ./app/Http/Controllers/HomeController.php
public function index(Request $request)
{
$request->user()->checkRoles('admin');

    return view('home');
}

</pre>

Now, only administrators should be able to see the dashboard. In a more complex application, we would use a middleware to do this instead.

We can test that this works by serving the application and logging in both user accounts; Samuel Jackson and Neo Ighodaro.

Remember that in our UserTableSeeder.php file, we defined Samuel as a regular user and Neo as an admin, so Samuel should see a 404 error after logging in and Neo should be able to see the homepage.


Testing the application

Let’s serve the application with this command:

<pre class="ql-syntax" spellcheck="false"> $ php artisan serve

</pre>

When we try logging in with Samuel’s credentials, we should see this:

On the other hand, we will get logged in with Neo’s credentials because he has an admin account:

We will also confirm that whenever a new user registers, he is assigned a role and it is the role of a regular user. We will create a new user and call him Greg, he should see a 404 error right after:

It works just as we wanted it to, however, it doesn’t really make any sense for us to redirect a regular user to a 404 page. Instead, we will edit the HomeController so that it redirects users based on their roles, that is, it redirects a regular user to a regular homepage and an admin to an admin dashboard.

Open the HomeController.php file and update the index method as seen below:

<pre class="ql-syntax" spellcheck="false"> // File: ./app/Http/Controllers/HomeController.php
public function index(Request $request)
{
if ($request->user()->hasRole('user')) {
return redirect('/');
}

    if ($request-&gt;user()-&gt;hasRole('admin')){
        return redirect('/admin/dashboard');
    }
}

</pre>

If we serve our application and try to log in using the admin account, we will hit a 404 error because we do not have a controller or a view for the admin/dashboard route. In the next article, we will start building the basic views for the CMS.


Conclusion

In this tutorial, we learned how to install a fresh Laravel app on our machine and pulled in all the needed dependencies. We also learned how to configure the Laravel app to work with a MySQL database. We also created our models and migrations files and seeded the database using database seeders.

In the next part of this series, we will start building the views for the application.

The source code for this project is available on Github.


Build a CMS with Laravel and Vue - Part 2: Implementing posts

In the previous part of this series, we set up user authentication and role authorization but we didn’t create any views for the application yet. In this section, we will create the Post model and start building the frontend for the application.

Our application allows different levels of accessibility for two kinds of users; the regular user and admin. In this chapter, we will focus on building the view that the regular users are permitted to see.

Before we build any views, let’s create the Post model as it is imperative to rendering the view.

The source code for this project is available here on GitHub.

Prerequisites

To follow along with this series, a few things are required:

  • Basic knowledge of PHP.
  • Basic knowledge of the Laravel framework.
  • Basic knowledge of JavaScript (ES6 syntax).
  • Basic knowledge of Vue.
  • Postman installed on your machine.

Setting up the Post model

We will create the Post model with an associated resource controller and a migration file using this command:

<pre class="ql-syntax" spellcheck="false"> $ php artisan make:model Post -mr

</pre>

We added the r flag because we want the controller to be a resource controller. The m flag will generate a migration for the model.

Let’s navigate into the database/migrations folder and update the CreatePostsTable class that was generated for us:

<pre class="ql-syntax" spellcheck="false"> // File: ./app/database/migrations/*_create_posts_table.php
<?php

// [...]

class CreatePostsTable extends Migration
{
    public function up()
    {
        Schema::create('posts', function (Blueprint $table) {
            $table-&gt;increments('id');
            $table-&gt;integer('user_id')-&gt;unsigned();
            $table-&gt;string('title');
            $table-&gt;text('body');
            $table-&gt;binary('image')-&gt;nullable();
            $table-&gt;timestamps();
        });
    }

    public function down()
    {
        Schema::dropIfExists('posts');
    }
}

</pre>

We included a user_id property because we want to create a relationship between the User and Post models. A Post also has an image field, which is where its associated image’s address will be stored.


Creating a database seeder for the Post table

We will create a new seeder file for the posts table using this command:

<pre class="ql-syntax" spellcheck="false"> $ php artisan make:seeder PostTableSeeder

</pre>

Let’s navigate into the database/seeds folder and update the PostTableSeeder.php file:

<pre class="ql-syntax" spellcheck="false"> // File: ./app/database/seeds/PostsTableSeeder.php
<?php

use App\Post;
use Illuminate\Database\Seeder;

class PostTableSeeder extends Seeder
{
    public function run()
    {
        $post = new Post;
        $post-&gt;user_id = 2;
        $post-&gt;title = "Using Laravel Seeders";
        $post-&gt;body = "Laravel includes a simple method of seeding your database with test data using seed classes. All seed classes are stored in the database/seeds directory. Seed classes may have any name you wish, but probably should follow some sensible convention, such as UsersTableSeeder, etc. By default, a DatabaseSeeder class is defined for you. From this class, you may use the  call method to run other seed classes, allowing you to control the seeding order.";
        $post-&gt;save();

        $post = new Post;
        $post-&gt;user_id = 2;
        $post-&gt;title = "Database: Migrations";
        $post-&gt;body = "Migrations are like version control for your database, allowing your team to easily modify and share the application's database schema. Migrations are typically paired with Laravel's schema builder to easily build your application's database schema. If you have ever had to tell a teammate to manually add a column to their local database schema, you've faced the problem that database migrations solve.";
        $post-&gt;save();
    }
}

</pre>

When we run this seeder, it will create two new posts and assign both of them to the admin user whose ID is 2. We are attaching both posts to the admin user because the regular users are only allowed to view posts and make comments; they can’t create a post.

Let’s open the DatabaseSeeder and update it with the following code:

<pre class="ql-syntax" spellcheck="false"> // File: ./app/database/seeds/DatabaseSeeder.php
<?php

use Illuminate\Database\Seeder;

class DatabaseSeeder extends Seeder
{
    public function run()
    {
        $this-&gt;call([
            RoleTableSeeder::class,
            UserTableSeeder::class,
            PostTableSeeder::class,
        ]);
    }
}

</pre>

We created the RoleTableSeeder and UserTableSeeder files in the previous chapter.

We will use this command to migrate our tables and seed the database:

<pre class="ql-syntax" spellcheck="false"> $ php artisan migrate:fresh --seed

</pre>

Defining the relationships

Just as we previously created a many-to-many relationship between the User and Role models, we need to create a different kind of relationship between the Post and User models.

We will define the relationship as a one-to-many relationship because a user will have many posts but a post will only ever belong to one user.

Open the User model and include the method below:

<pre class="ql-syntax" spellcheck="false"> // File: ./app/User.php
public function posts()
{
return $this->hasMany(Post::class);
}
</pre>

Open the Post model and include the method below:

<pre class="ql-syntax" spellcheck="false"> // File: ./app/Post.php
public function user()
{
return $this->belongsTo(User::class);
}
</pre>

Setting up the routes

At this point in our application, we do not have a front page with all the posts listed. Let’s create so anyone can see all of the created posts. Asides from the front page, we also need a single post page in case a user needs to read a specific post.

Let’s include two new routes to our routes/web.php file:

  • The first route will match requests to the root of our application and will be handled by the [email protected] action:
<pre class="ql-syntax" spellcheck="false"> Route::get('/', '[email protected]');
</pre>
In the routes/web.php file, there will already be a route definition for the / address, you will have to replace it with the new route definition above.
  • The second route will handle requests for specific Post items and will be handled by the [email protected] action:
<pre class="ql-syntax" spellcheck="false"> Route::get('/posts/{post}', '[email protected]');
</pre>

With these two new routes added, here’s what the routes/web.php file should look like this:

<pre class="ql-syntax" spellcheck="false"> // File: ./routes/web.php
<?php

Auth::routes();
Route::get('/posts/{post}', '[email protected]');
Route::get('/home', '[email protected]')-&gt;name('home');
Route::get('/', '[email protected]');

</pre>

Setting up the Post controller

In this section, we want to define the handler action methods that we registered in the routes/web.php file so that our application know how to render the matching views.

First, let’s add the all() method:

<pre class="ql-syntax" spellcheck="false"> // File: ./app/Http/Controllers/PostController.php
public function all()
{
return view('landing', [
'posts' => Post::latest()->paginate(5)
]);
}
</pre>

Here, we want to retrieve five created posts per page and send to the landing view. We will create this view shortly.

Next, let’s add the single() method to the controller:

<pre class="ql-syntax" spellcheck="false"> // File: ./app/Http/Controllers/PostController.php
public function single(Post $post)
{
return view('single', compact('post'));
}
</pre>

In the method above, we used a feature of Laravel named route model binding to map the URL parameter to a Post instance with the same ID. We are returning a single view, which we will create shortly. This will be the view for the single post page.


Building our views

Laravel uses a templating engine called Blade for its frontend. We will use Blade to build these parts of the frontend before switching to Vue in the next chapter.

Navigate to the resources/views folder and create two new Blade files:

  1. landing.blade.php
  2. single.blade.php

These are the files that will load the views for the landing page and single post page. Before we start writing any code in these files, we want to create a simple layout template that our page views can use as a base.

In the resources/views/layouts folder, create a Blade template file and call it master.blade.php. This is where we will define the inheritable template for our single and landing pages.

Open the master.blade.php file and update it with this code:

<pre class="ql-syntax" spellcheck="false"> <!-- File: ./resources/views/layouts/master.blade.php -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="">
<meta name="author" content="Neo Ighodaro">
<title>LaravelCMS</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css">
<style>
body {
padding-top: 54px;
}
@media (min-width: 992px) {
body {
padding-top: 56px;
}
}
</style>
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-dark bg-dark fixed-top">
<div class="container">
<a class="navbar-brand" href="/">LaravelCMS</a>
<div class="collapse navbar-collapse" id="navbarResponsive">
<ul class="navbar-nav ml-auto">
@if (Route::has('login'))
@auth
<li class="nav-item">
<a class="nav-link" href="{{ url('/home') }}">Home</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ route('logout') }}"
onclick="event.preventDefault();
document.getElementById('logout-form').submit();">
Log out
</a>
<form id="logout-form" action="{{ route('logout') }}" method="POST" style="display: none;">
@csrf
</form>
</li>
@else
<li class="nav-item">
<a class="nav-link" href="{{ route('login') }}">Login</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ route('register') }}">Register</a>
</li>
@endauth
@endif
</ul>
</div>
</div>
</nav>

    &lt;div id="app"&gt;
        @yield('content')
    &lt;/div&gt;

    &lt;footer class="py-5 bg-dark"&gt;
      &lt;div class="container"&gt;
        &lt;p class="m-0 text-center text-white"&gt;Copyright &amp;copy; LaravelCMS 2018&lt;/p&gt;
      &lt;/div&gt;
    &lt;/footer&gt;
  &lt;/body&gt;
&lt;/html&gt;

</pre>

Now we can inherit this template in the landing.blade.php file, open it and update it with this code:

<pre class="ql-syntax" spellcheck="false"> {{-- File: ./resources/views/landing.blade.php --}}
@extends('layouts.master')

@section('content')
&lt;div class="container"&gt;
  &lt;div class="row align-items-center"&gt;
    &lt;div class="col-md-8 mx-auto"&gt;
      &lt;h1 class="my-4 text-center"&gt;Welcome to the Blog &lt;/h1&gt;

      @foreach ($posts as $post)
      &lt;div class="card mb-4"&gt;
        &lt;img class="card-img-top" src=" {!! !empty($post-&gt;image) ? '/uploads/posts/' . $post-&gt;image :  'http://placehold.it/750x300' !!} " alt="Card image cap"&gt;
        &lt;div class="card-body"&gt;
          &lt;h2 class="card-title text-center"&gt;{{ $post-&gt;title }}&lt;/h2&gt;
          &lt;p class="card-text"&gt; {{ str_limit($post-&gt;body, $limit = 280, $end = '...') }} &lt;/p&gt;
          &lt;a href="/posts/{{ $post-&gt;id }}" class="btn btn-primary"&gt;Read More &amp;rarr;&lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="card-footer text-muted"&gt;
          Posted {{ $post-&gt;created_at-&gt;diffForHumans() }} by
          &lt;a href="#"&gt;{{ $post-&gt;user-&gt;name }} &lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;
      @endforeach

    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;
@endsection

</pre>

Let’s do the same with the single.blade.php file, open it and update it with this code:

<pre class="ql-syntax" spellcheck="false"> {{-- File: ./resources/views/single.blade.php --}}
@extends('layouts.master')

@section('content')
&lt;div class="container"&gt;
  &lt;div class="row"&gt;
    &lt;div class="col-lg-10 mx-auto"&gt;
      &lt;h3 class="mt-4"&gt;{{ $post-&gt;title }} &lt;span class="lead"&gt; by &lt;a href="#"&gt; {{ $post-&gt;user-&gt;name }} &lt;/a&gt;&lt;/span&gt; &lt;/h3&gt;
      &lt;hr&gt;
      &lt;p&gt;Posted {{ $post-&gt;created_at-&gt;diffForHumans() }} &lt;/p&gt;
      &lt;hr&gt;
      &lt;img class="img-fluid rounded" src=" {!! !empty($post-&gt;image) ? '/uploads/posts/' . $post-&gt;image :  'http://placehold.it/750x300' !!} " alt=""&gt;
      &lt;hr&gt;
      &lt;p class="lead"&gt;{{ $post-&gt;body }}&lt;/p&gt;
      &lt;hr&gt;
      &lt;div class="card my-4"&gt;
        &lt;h5 class="card-header"&gt;Leave a Comment:&lt;/h5&gt;
        &lt;div class="card-body"&gt;
          &lt;form&gt;
            &lt;div class="form-group"&gt;
              &lt;textarea class="form-control" rows="3"&gt;&lt;/textarea&gt;
            &lt;/div&gt;
            &lt;button type="submit" class="btn btn-primary"&gt;Submit&lt;/button&gt;
          &lt;/form&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;
@endsection

</pre>

Testing the application

We can test the application to see that things work as we expect. When we serve the application, we expect to see a landing page and a single post page. We also expect to see two posts because that’s the number of posts we seeded into the database.

We will serve the application using this command:

<pre class="ql-syntax" spellcheck="false"> $ php artisan serve

</pre>

We can visit this address to see the application:

We have used simple placeholder images here because we haven’t built the admin dashboard that allows CRUD operations to be performed on posts.

In the coming chapters, we will add the ability for an admin to include a custom image when creating a new post.


Conclusion

In this chapter, we created the Post model and defined a relationship on it to the User model. We also built the landing page and single page.

In the next part of this series, we will develop the API that will be the medium for communication between the admin user and the post items.

The source code for this project is available here on Github.


Build a CMS with Laravel and Vue - Part 3: Building an API

In the previous part of this series, we initialized the posts resource and started building the frontend of the CMS. We designed the front page that shows all the posts and the single post page using Laravel’s templating engine, Blade.

In this part of the series, we will start building the API for the application. We will create an API for CRUD operations that an admin will perform on posts and we will test the endpoints using Postman.

The source code for this project is available here on GitHub.

Prerequisites

To follow along with this series, a few things are required:

  • Basic knowledge of PHP.
  • Basic knowledge of the Laravel framework.
  • Basic knowledge of JavaScript (ES6 syntax).
  • Basic knowledge of Vue.
  • Postman installed on your machine.

Building the API using Laravel’s API resources

The Laravel framework makes it very easy to build APIs. It has an API resources feature that we can easily adopt in our project. You can think of API resources as a transformation layer between Eloquent models and the JSON responses that will be sent back by our API.


Allowing mass assignment on specified fields

Since we are going to be performing CRUD operations on the posts in the application, we have to explicitly specify that it’s permitted for some fields to be mass-assigned data. For security reasons, Laravel prevents mass assignment of data to model fields by default.

Open the Post.php file and include this line of code:

<pre class="ql-syntax" spellcheck="false"> // File: ./app/Post.php
protected $fillable = ['user_id', 'title', 'body', 'image'];
</pre>

Defining API routes

We will use the apiResource()method to generate only API routes. Open the routes/api.php file and add the following code:

<pre class="ql-syntax" spellcheck="false"> // File: ./routes/api.php
Route::apiResource('posts', 'PostController');

</pre>

Because we will be handling the API requests on the /posts URL using the PostController, we will have to include some additional action methods in our post controller.

Creating the Post resource

At the beginning of this section, we already talked about what Laravel’s API resources are. Here, we create a resource class for our Post model. This will enable us to retrieve Post data and return formatted JSON format.

To create a resource class for our Post model run the following command in your terminal:

<pre class="ql-syntax" spellcheck="false"> $ php artisan make:resource PostResource

</pre>

A new PostResource.php file will be available in the app/Http/Resources directory of our application. Open up the PostResource.php file and replace the toArray() method with the following:

<pre class="ql-syntax" spellcheck="false"> // File: ./app/Http/Resources/PostResource.php
public function toArray($request)
{
return [
'id' => $this->id,
'title' => $this->title,
'body' => $this->body,
'image' => $this->image,
'created_at' => (string) $this->created_at,
'updated_at' => (string) $this->updated_at,
];
}
</pre>

The job of this toArray() method is to convert our P``ost resource into an array. As seen above, we have specified the fields on our Post model, which we want to be returned as JSON when we make a request for posts.

We are also explicitly casting the dates, created_at and update_at, to strings so that they would be returned as date strings. The dates are normally an instance of Carbon.

Now that we have created a resource class for our Post model, we can start building the API’s action methods in our PostController and return instances of the PostResource where we want.


Adding the action methods to the Post controller

The usual actions performed on a post include the following:

  1. Create - the process of creating a new post.
  2. Read - the process of reading one or more posts.
  3. Update - the process of updating an already published post.
  4. Delete - the process of deleting a post.

In the last article, we already implemented a kind of ‘Read’ functionality when we defined the all and single methods. These methods allow users to browse through posts on the homepage.

In this section, we will define the methods that will resolve our API requests for creating, reading, updating and deleting posts.

The first thing we want to do is import the PostResource class at the top of the PostController.php file:

<pre class="ql-syntax" spellcheck="false"> // File: ./app/Http/Controllers/PostController.php
use App\Http\Resources\PostResource;
</pre>
Because we created the PostController as a resource controller, we already have the resource action methods included for us in the PostController.php file, we will be updating them with fitting snippets of code.

Building the handler action for the create operation

In the PostController update the store() action method with the code snippet below. It will allow us to validate and create a new post:

<pre class="ql-syntax" spellcheck="false"> // File: ./app/Http/Controllers/PostController.php
public function store(Request $request)
{
$this->validate($request, [
'title' => 'required',
'body' => 'required',
'user_id' => 'required',
'image' => 'required|mimes:jpeg,png,jpg,gif,svg',
]);

    $post = new Post;

    if ($request-&gt;hasFile('image')) {
        $image = $request-&gt;file('image');
        $name = str_slug($request-&gt;title).'.'.$image-&gt;getClientOriginalExtension();
        $destinationPath = public_path('/uploads/posts');
        $imagePath = $destinationPath . "/" . $name;
        $image-&gt;move($destinationPath, $name);
        $post-&gt;image = $name;
    }

    $post-&gt;user_id = $request-&gt;user_id;
    $post-&gt;title = $request-&gt;title;
    $post-&gt;body = $request-&gt;body;
    $post-&gt;save();

    return new PostResource($post);
}

</pre>

Here’s a breakdown of what this method does:

  1. Receives a new request.
  2. Validates the request.
  3. Creates a new post.
  4. Returns the post as a PostResource, which in turn returns a JSON formatted response.

Building the handler action for the read operations

What we want here is to be able to read all the created posts or a single post. This is possible because the apiResource() method defines the API routes using standard REST rules.

This means that a GET request to this address, http://127.0.0.1:800/api/posts, should be resolved by the index() action method. Let’s update the index method with the following code:

<pre class="ql-syntax" spellcheck="false"> // File: ./app/Http/Controllers/PostController.php
public function index()
{
return PostResource::collection(Post::latest()->paginate(5));
}
</pre>

This method will allow us to return a JSON formatted collection of all of the stored posts. We also want to paginate the response as this will allow us to create a better view on the admin dashboard.

Following the RESTful conventions as we discussed above, a GET request to this address, http://127.0.0.1:800/api/posts/id, should be resolved by the show() action method. Let’s update the method with the fitting snippet:

<pre class="ql-syntax" spellcheck="false"> // File: ./app/Http/Controllers/PostController.php
public function show(Post $post)
{
return new PostResource($post);
}
</pre>

Awesome, now this method will return a single instance of a post resource upon API query.


Building the handler action for the update operation

Next, let’s update the update() method in the PostController class. It will allow us to modify an existing post:

<pre class="ql-syntax" spellcheck="false"> // File: ./app/Http/Controllers/PostController.php
public function update(Request $request, Post $post)
{
$this->validate($request, [
'title' => 'required',
'body' => 'required',
]);

    $post-&gt;update($request-&gt;only(['title', 'body']));

    return new PostResource($post);
}

</pre>

This method receives a request and a post id as parameters, then we use route model binding to resolve the id into an instance of a Post. First, we validate the $request attributes, then we update the title and body fields of the resolved post.


Building the handler action for the delete operation

Let’s update the destroy() method in the PostController class. This method will allow us to remove an existing post:

<pre class="ql-syntax" spellcheck="false"> // File: ./app/Http/Controllers/PostController.php
public function destroy(Post $post)
{
$post->delete();

    return response()-&gt;json(null, 204);
}

</pre>

In this method, we resolve the Post instance, then delete it and return a 204 response code.

Our methods are complete. We have a method to handle our CRUD operations, however, we haven’t built the frontend for the admin dashboard.

At the end of the second article, we defined the [email protected]() action method like this:

<pre class="ql-syntax" spellcheck="false"> public function index(Request $request)
{
if ($request->user()->hasRole('user')) {
return view('home');
}

    if ($request-&gt;user()-&gt;hasRole('admin')) {
        return redirect('/admin/dashboard');
    }
}

</pre>

This allowed us to redirect regular users to the view home, and admin users to the URL /admin/dashboard. At this point in this series, a visit to /admin/dashboard will fail because we have neither defined it as a route with a handler Controller nor built a view for it.

Let’s create the AdminController with this command:

<pre class="ql-syntax" spellcheck="false"> $ php artisan make:controller AdminController

</pre>

We will add the /admin/ route to our routes/web.php file:

<pre class="ql-syntax" spellcheck="false"> Route::get('/admin/{any}', '[email protected]')->where('any', '.*');
</pre>
Note that we wrote /admin/{any} here because we intend to serve every page of the admin dashboard using the Vue router. When we start building the admin dashboard in the next article, we will let Vue handle all the routes of the /admin pages.

Let’s update the AdminController.php file to use the auth middleware and include an index() action method:

<pre class="ql-syntax" spellcheck="false"> // File: ./app/Http/Controllers/AdminController.php
<?php

namespace App\Http\Controllers;

class AdminController extends Controller
{
    public function __construct()
    {
        $this-&gt;middleware('auth');
    }

    public function index()
    {
        if (request()-&gt;user()-&gt;hasRole('admin')) {
            return view('admin.dashboard');
        }

        if (request()-&gt;user()-&gt;hasRole('user')) {
            return redirect('/home');
        }
    }
}

</pre>

In the index()action method, we included a snippet that will ensure that only admin users can visit the admin dashboard and perform CRUD operations on posts.

We will not start building the admin dashboard in this article but will test that our API works properly. We will use Postman to make requests to the application.


Testing the application

Let’s test that our API works as expected. We will, first of all, serve the application using this command:

<pre class="ql-syntax" spellcheck="false"> $ php artisan serve

</pre>

We can visit http://localhost:8000 to see our application and there should be exactly two posts available; these are the posts we seeded into the database during the migration:

When testing with Postman always set the Content-Type header to application/json.

Now let’s create a new post over the API interface using Postman. Send a POST request as seen below:

Now let’s update this post we just created. In Postman, we will pass only the title and body fields to a PUT request.

To make it easy, you can just copy the payload below and use the raw request data type for the Body:

<pre class="ql-syntax" spellcheck="false"> {
"title": "We made an edit to the Post on APIs",
"body": "To a developer, 'What's an API?' might be a straightforward - if not exactly simple - question. But to anyone who doesn't have experience with code. APIs can come across as confusing or downright intimidating."
}

</pre>

We could have used the PATCH method to make this update, the PUT and PATCH HTTP verbs both work well for editing an already existing item.

Finally, let’s delete the post using Postman:

We are sure the post is deleted because the response status is 204 No Content as we specified in the PostController.


Conclusion

In this chapter, we learned about Laravel’s API resources and we created a resource class for the Post model. We also used the apiResources() method to generate API only routes for our application. We wrote the methods to handle the API operations and tested them using Postman.

In the next part, we will build the admin dashboard and develop the logic that will enable the admin user to manage posts over the API.

The source code for this project is available here on Github.


Build a CMS with Laravel and Vue - Part 4: Building the dashboard

In the last article of this series, we built the API interface and used Laravel API resources to return neatly formatted JSON responses. We tested that the API works as we defined it to using Postman.

In this part of the series, we will start building the admin frontend of the CMS. This is the first part of the series where we will integrate Vue and explore Vue’s magical abilities.

When we are done with this part, our application will have some added functionalities as seen below:

The source code for this project is available here on GitHub.

Prerequisites

To follow along with this series, a few things are required:

  • Basic knowledge of PHP.
  • Basic knowledge of the Laravel framework.
  • Basic knowledge of JavaScript (ES6 syntax).
  • Basic knowledge of Vue.

Building the frontend

Laravel ships with Vue out of the box so we do not need to use the Vue-CLI or reference Vue from a CDN. This makes it possible for us to have all of our application, the frontend, and backend, in a single codebase.

Every newly created instance of a Laravel installation has some Vue files included by default, we can see these files when we navigate into the resources/assets/js/components folder.


Setting up Vue and VueRouter

Before we can start using Vue in our application, we need to first install some dependencies using NPM. To install the dependencies that come by default with Laravel, run the command below:

<pre class="ql-syntax" spellcheck="false"> $ npm install

</pre>

We will be managing all of the routes for the admin dashboard using vue-router so let’s pull it in:

<pre class="ql-syntax" spellcheck="false"> $ npm install --save vue-router

</pre>

When the installation is complete, the next thing we want to do is open the resources/assets/js/app.js file and replace its contents with the code below:

<pre class="ql-syntax" spellcheck="false"> // File: ./resources/assets/js/app.js
require('./bootstrap');

import Vue from 'vue'
import VueRouter from 'vue-router'
import Homepage from './components/Homepage'
import Read from './components/Read'

Vue.use(VueRouter)

const router = new VueRouter({
    mode: 'history',
    routes: [
        {
            path: '/admin/dashboard',
            name: 'read',
            component: Read,
            props: true
        },
    ],
});

const app = new Vue({
    el: '#app',
    router,
    components: { Homepage },
});

</pre>

In the snippet above, we imported the VueRouter and added it to the Vue application. We also imported a Homepage and a Read component. These are the components where we will write our markup so let’s create both files.

Open the resources/assets/js/components folder and create four files:

  1. Homepage.vue - this will be the parent component for the admin dashboard frontend.
  2. Read.vue - this will be component that displays all the available posts on the admin dashboard.
  3. Create.vue - this will be the component where an admin user can create a new post.
  4. Update.vue - this will be the component that displays the view where an admin user can update an existing post.
Note that we didn’t create a component file for the delete operation, this is because it is going to be possible to delete a post from the Read component. There is no need for a view.

In the resources/assets/js/app.js file, we defined a routes array and in it, we registered a read route. During render time, this route’s path will be mapped to the Read component.

In the previous article, we specified that admin users should be shown an admin.dashboard view in the index method, however, we didn’t create this view. Let’s create the view. Open the resources/views folder and create a new folder called admin. Within the new resources/views/admin folder, create a new file and called dashboard.blade.php. This is going to be the entry point to the admin dashboard, further from this route, we will let the VueRouter handle everything else.

Open the resources/views/admin/dashboard.blade.php file and paste in the following code:

<pre class="ql-syntax" spellcheck="false"> <!-- File: ./resources/views/admin/dashboard.blade.php -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title> Welcome to the Admin dashboard </title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css">
<style>
html, body {
background-color: #202B33;
color: #738491;
font-family: "Open Sans";
font-size: 16px;
font-smoothing: antialiased;
overflow: hidden;
}
</style>
</head>
<body>

  &lt;script src="{{ asset('js/app.js') }}"&gt;&lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;

</pre>

Our goal here is to integrate Vue into the application, so we included the resources/assets/js/app.js file with this line of code:

<pre class="ql-syntax" spellcheck="false"> <script src="{{ asset('js/app.js') }}"></script>

</pre>

For our app to work, we need a root element to bind our Vue instance unto. Before the <script> tag, add this snippet of code:

<pre class="ql-syntax" spellcheck="false"> <div id="app">
<Homepage
:user-name='@json(auth()->user()->name)'
:user-id='@json(auth()->user()->id)'
></Homepage>
</div>
</pre>

We earlier defined the Homepage component as the wrapping component, that’s why we pulled it in here as the root component. For some of the frontend components to work correctly, we require some details of the logged in admin user to perform CRUD operations. This is why we passed down the userName and userId props to the Homepage component.

We need to prevent the CSRF error from occurring in our Vue frontend, so include this snippet of code just before the <title> tag:

<pre class="ql-syntax" spellcheck="false"> <meta name="csrf-token" content="{{ csrf_token() }}">
<script> window.Laravel = { csrfToken: 'csrf_token() ' } </script>
</pre>

This snippet will ensure that the correct token is always included in our frontend, Laravel provides the CSRF protection for us out of the box.

At this point, this should be the contents of your resources/views/admin/dashboard.blade.php file:

<pre class="ql-syntax" spellcheck="false"> <!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<meta name="csrf-token" content="{{ csrf_token() }}">
<script> window.Laravel = { csrfToken: 'csrf_token() ' } </script>
<title> Welcome to the Admin dashboard </title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css">
<style>
html, body {
background-color: #202B33;
color: #738491;
font-family: "Open Sans";
font-size: 16px;
font-smoothing: antialiased;
overflow: hidden;
}
</style>
</head>
<body>
<div id="app">
<Homepage
:user-name='@json(auth()->user()->name)'
:user-id='@json(auth()->user()->id)'>
</Homepage>
</div>
<script src="{{ asset('js/app.js') }}"></script>
</body>
</html>
</pre>

Setting up the Homepage view

Open the Homepage.vue file that we created some time ago and include this markup template:

<pre class="ql-syntax" spellcheck="false"> <!-- File: ./resources/app/js/components/Homepage.vue -->
<template>
<div>
<nav>
<section>
<a style="color: white" href="/admin/dashboard">Laravel-CMS</a> &nbsp; || &nbsp;
<a style="color: white" href="/">HOME</a>
<hr>
<ul>
<li>
<router-link :to="{ name: 'create', params: { userId } }">
NEW POST
</router-link>
</li>
</ul>
</section>
</nav>
<article>
<header>
<header class="d-inline">Welcome, {{ userName }}</header>
<p @click="logout" class="float-right mr-3" style="cursor: pointer">Logout</p>
</header>
<div>
<router-view></router-view>
</div>
</article>
</div>
</template>
</pre>

We added a router-link in this template, which routes to the Create component.

We are passing the userId data to the create component because a userId is required during Post creation.

Let’s include some styles so that the page looks good. Below the closing template tag, paste the following code:

<pre class="ql-syntax" spellcheck="false"> <style scoped>
@import url(https://fonts.googleapis.com/css?family=Dosis:300|Lato:300,400,600,700|Roboto+Condensed:300,700|Open+Sans+Condensed:300,600|Open+Sans:400,300,600,700|Maven+Pro:400,700);
@import url("https://netdna.bootstrapcdn.com/font-awesome/4.2.0/css/font-awesome.css");
* {
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
box-sizing: border-box;
}
header {
color: #d3d3d3;
}
nav {
position: absolute;
top: 0;
bottom: 0;
right: 82%;
left: 0;
padding: 22px;
border-right: 2px solid #161e23;
}
nav > header {
font-weight: 700;
font-size: 0.8rem;
text-transform: uppercase;
}
nav section {
font-weight: 600;
}
nav section header {
padding-top: 30px;
}
nav section ul {
list-style: none;
padding: 0px;
}
nav section ul a {
color: white;
text-decoration: none;
font-weight: bold;
}
article {
position: absolute;
top: 0;
bottom: 0;
right: 0;
left: 18%;
overflow: auto;
border-left: 2px solid #2a3843;
padding: 20px;
}
article > header {
height: 60px;
border-bottom: 1px solid #2a3843;
}
</style>
</pre>
We are using the scoped attribute on the <style> tag because we want the CSS to only be applied on the Homepage component.

Next, let’s add the <script> section that will use the props we passed down from the parent component. We will also define the method that controls the log out feature here. Below the closing style tag, paste the following code:

<pre class="ql-syntax" spellcheck="false"> <script>
export default {
props: {
userId: {
type: Number,
required: true
},
userName: {
type: String,
required: true
}
},
data() {
return {};
},
methods: {
logout() {
axios.post("/logout").then(() => {
window.location = "/";
});
}
}
};
</script>
</pre>

Setting up the Read view

In the resources/assets/js/app.js file, we defined the path of the read component as /admin/dashboard, which is the same address as the Homepage component. This will make sure the Read component always loads by default.

In the Read component, we want to load all of the available posts. We are also going to add Update and Delete options to each post. Clicking on these options will lead to the update and delete views respectively.

Open the Read.vue file and paste the following:

<pre class="ql-syntax" spellcheck="false"> <!-- File: ./resources/app/js/components/Read.vue -->
<template>
<div id="posts">
<p class="border p-3" v-for="post in posts">
{{ post.title }}
<router-link :to="{ name: 'update', params: { postId : post.id } }">
<button type="button" class="p-1 mx-3 float-right btn btn-light">
Update
</button>
</router-link>
<button
type="button"
@click="deletePost(post.id)"
class="p-1 mx-3 float-right btn btn-danger"
>
Delete
</button>
</p>
<div>
<button
v-if="next"
type="button"
@click="navigate(next)"
class="m-3 btn btn-primary"
>
Next
</button>
<button
v-if="prev"
type="button"
@click="navigate(prev)"
class="m-3 btn btn-primary"
>
Previous
</button>
</div>
</div>
</template>
</pre>

Above, we have the template to handle the posts that are loaded from the API. Next, paste the following below the closing template tag:

<pre class="ql-syntax" spellcheck="false"> <script>
export default {
mounted() {
this.getPosts();
},
data() {
return {
posts: {},
next: null,
prev: null
};
},
methods: {
getPosts(address) {
axios.get(address ? address : "/api/posts").then(response => {
this.posts = response.data.data;
this.prev = response.data.links.prev;
this.next = response.data.links.next;
});
},
deletePost(id) {
axios.delete("/api/posts/" + id).then(response => this.getPosts())
},
navigate(address) {
this.getPosts(address)
}
}
};
</script>
</pre>

In the script above, we defined a getPosts() method that requests a list of posts from the backend server. We also defined a posts object as a data property. This object will be populated whenever posts are received from the backend server.

We defined next and prev data string properties to store pagination links and only display the pagination options where it is available.

Lastly, we defined a deletePost() method that takes the id of a post as a parameter and sends a DELETE request to the API interface using Axios.


Testing the application

Now that we have completed the first few components, we can serve the application using this command:

<pre class="ql-syntax" spellcheck="false"> $ php artisan serve

</pre>

We will also build the assets so that our JavaScript is compiled for us. To do this, will run the command below in the root of the project folder:

<pre class="ql-syntax" spellcheck="false"> $ npm run dev

</pre>

We can visit the application’s URL http://localhost:8000 and log in as an admin user, and delete a post:


Conclusion

In this part of the series, we started building the admin dashboard using Vue. We installed VueRouter to make the admin dashboard a SPA. We added the homepage view of the admin dashboard and included read and delete functionalities.

We are not done with the dashboard just yet. In the next part, we will add the views that lets us create and update posts.

The source code for this project is available here on Github.


Build a CMS with Laravel and Vue - Part 5: Completing our dashboards

In the previous part of this series, we built the first parts of the admin dashboard using Vue. We also made it into an SPA with the VueRouter, this means that visiting the pages does not cause a reload to the web browser.

We only built the wrapper component and the Read component that retrieves the posts to be loaded so an admin can manage them.

Here’s a recording of what we ended up with, in the last article:

In this article, we will build the view that will allow users to create and update posts. We will start writing code in the Update.vue and Create.vue files that we created in the previous article.

When we are done with this part, we will have additional functionalities like create and updating:

The source code for this project is available here on Github.

Prerequisites

To follow along with this series, a few things are required:

  • Basic knowledge of PHP.
  • Basic knowledge of the Laravel framework.
  • Basic knowledge of JavaScript (ES6 syntax).
  • Basic knowledge of Vue.

Including the new routes in VueRouter

In the previous article, we only defined the route for the Read component, we need to include the route configuration for the new components that we are about to build; Update and Create.

Open the resources/assets/js/app.js file and replace the contents with the code below:

<pre class="ql-syntax" spellcheck="false"> require('./bootstrap');

import Vue from 'vue'
import VueRouter from 'vue-router'
import Homepage from './components/Homepage'
import Create from './components/Create'
import Read from './components/Read'
import Update from './components/Update'

Vue.use(VueRouter)

const router = new VueRouter({
    mode: 'history',
    routes: [
        {
            path: '/admin/dashboard',
            name: 'read',
            component: Read,
            props: true
        },
        {
            path: '/admin/create',
            name: 'create',
            component: Create,
            props: true
        },
        {
            path: '/admin/update',
            name: 'update',
            component: Update,
            props: true
        },
    ],
});

const app = new Vue({
    el: '#app',
    router,
    components: { Homepage },
});

</pre>

Above, we have added two new components to the JavaScript file. We have the Create and Read components. We also added them to the router so that they can be loaded using the specified URLs.


Building the create view

Open the Create.vue file and update it with this markup template:

<pre class="ql-syntax" spellcheck="false"> <!-- File: ./resources/app/js/components/Create.vue -->
<template>
<div class="container">
<form>
<div :class="['form-group m-1 p-3', (successful ? 'alert-success' : '')]">
<span v-if="successful" class="label label-sucess">Published!</span>
</div>
<div :class="['form-group m-1 p-3', error ? 'alert-danger' : '']">
<span v-if="errors.title" class="label label-danger">
{{ errors.title[0] }}
</span>
<span v-if="errors.body" class="label label-danger">
{{ errors.body[0] }}
</span>
<span v-if="errors.image" class="label label-danger">
{{ errors.image[0] }}
</span>
</div>

      &lt;div class="form-group"&gt;
        &lt;input type="title" ref="title" class="form-control" id="title" placeholder="Enter title" required&gt;
      &lt;/div&gt;

      &lt;div class="form-group"&gt;
        &lt;textarea class="form-control" ref="body" id="body" placeholder="Enter a body" rows="8" required&gt;&lt;/textarea&gt;
      &lt;/div&gt;

      &lt;div class="custom-file mb-3"&gt;
        &lt;input type="file" ref="image" name="image" class="custom-file-input" id="image" required&gt;
        &lt;label class="custom-file-label" &gt;Choose file...&lt;/label&gt;
      &lt;/div&gt;

      &lt;button type="submit" @click.prevent="create" class="btn btn-primary block"&gt;
        Submit
      &lt;/button&gt;
    &lt;/form&gt;
  &lt;/div&gt;
&lt;/template&gt;

</pre>

Above we have the template for the Create component. If there is an error during post creation, there will be a field indicating the specific error. When a post is successfully published, there will also a message saying it was successful.

Let’s include the script logic that will perform the sending of posts to our backend server and read back the response.

After the closing template tag add this:

<pre class="ql-syntax" spellcheck="false"> <script>
export default {
props: {
userId: {
type: Number,
required: true
}
},
data() {
return {
error: false,
successful: false,
errors: []
};
},
methods: {
create() {
const formData = new FormData();
formData.append("title", this.$refs.title.value);
formData.append("body", this.$refs.body.value);
formData.append("user_id", this.userId);
formData.append("image", this.$refs.image.files[0]);

      axios
        .post("/api/posts", formData)
        .then(response =&gt; {
          this.successful = true;
          this.error = false;
          this.errors = [];
        })
        .catch(error =&gt; {
          if (!_.isEmpty(error.response)) {
            if ((error.response.status = 422)) {
              this.errors = error.response.data.errors;
              this.successful = false;
              this.error = true;
            }
          }
        });

      this.$refs.title.value = "";
      this.$refs.body.value = "";
    }
  }
};
&lt;/script&gt;

</pre>

In the script above, we defined a create() method that takes the values of the input fields and uses the Axios library to send them to the API interface on the backend server. Within this method, we also update the status of the operation, so that an admin user can know when a post is created successfully or not.


Building the update view

Let’s start building the Update component. Open the Update.vue file and update it with this markup template:

<pre class="ql-syntax" spellcheck="false"> <!-- File: ./resources/app/js/components/Update.vue -->
<template>
<div class="container">
<form>
<div :class="['form-group m-1 p-3', successful ? 'alert-success' : '']">
<span v-if="successful" class="label label-sucess">Updated!</span>
</div>

      &lt;div :class="['form-group m-1 p-3', error ? 'alert-danger' : '']"&gt;
        &lt;span v-if="errors.title" class="label label-danger"&gt;
          {{ errors.title[0] }}
        &lt;/span&gt;
        &lt;span v-if="errors.body" class="label label-danger"&gt;
          {{ errors.body[0] }}
        &lt;/span&gt;
      &lt;/div&gt;

      &lt;div class="form-group"&gt;
        &lt;input type="title" ref="title" class="form-control" id="title" placeholder="Enter title" required&gt;
      &lt;/div&gt;

      &lt;div class="form-group"&gt;
        &lt;textarea class="form-control" ref="body" id="body" placeholder="Enter a body" rows="8" required&gt;&lt;/textarea&gt;
      &lt;/div&gt;

      &lt;button type="submit" @click.prevent="update" class="btn btn-primary block"&gt;
        Submit
      &lt;/button&gt;
    &lt;/form&gt;
  &lt;/div&gt;
&lt;/template&gt;

</pre>

This template is similar to the one in the Create component. Let’s add the script for the component.

Below the closing template tag, paste the following:

<pre class="ql-syntax" spellcheck="false"> <script>
export default {
mounted() {
this.getPost();
},
props: {
postId: {
type: Number,
required: true
}
},
data() {
return {
error: false,
successful: false,
errors: []
};
},
methods: {
update() {
let title = this.$refs.title.value;
let body = this.$refs.body.value;

      axios
        .put("/api/posts/" + this.postId, { title, body })
        .then(response =&gt; {
          this.successful = true;
          this.error = false;
          this.errors = [];
        })
        .catch(error =&gt; {
          if (!_.isEmpty(error.response)) {
            if ((error.response.status = 422)) {
              this.errors = error.response.data.errors;
              this.successful = false;
              this.error = true;
            }
          }
        });
    },
    getPost() {
      axios.get("/api/posts/" + this.postId).then(response =&gt; {
        this.$refs.title.value = response.data.data.title;
        this.$refs.body.value = response.data.data.body;
      });
    }
  }
};
&lt;/script&gt;

</pre>

In the script above, we make a call to the getPosts() method as soon as the component is mounted. The getPosts() method fetches the data of a single post from the backend server, using the postId.

When Axios sends back the data for the post, we update the input fields in this component so they can be updated.

Finally, the update() method takes the values of the fields in the components and attempts to send them to the backend server for an update. In a situation where the fails, we get instant feedback.


Testing the application

To test that our changes work, we want to refresh the database and restore it back to a fresh state. To do this, run the following command in your terminal:

<pre class="ql-syntax" spellcheck="false"> $ php artisan migrate:fresh --seed

</pre>

Next, let’s compile our JavaScript files and assets. This will make sure all the changes we made in the Vue component and the app.js file gets built. To recompile, run the command below in your terminal:

<pre class="ql-syntax" spellcheck="false"> $ npm run dev

</pre>

Lastly, we need to serve the application. To do this, run the following command in your terminal window:

<pre class="ql-syntax" spellcheck="false"> $ php artisan serve

</pre>

If you had the serve command running before, then you might need to restart it.

We will visit the application’s http://localhost:8000 and log in as an admin user. From the dashboard, you can test the create and update feature:


Conclusion

In this part of the series, we updated the dashboard to include the Create and Update component so the administrator can add and update posts.

In the next article, we will build the views that allow for the creation and updating of a post.

The source code for this project is available here on Github.


Build a CMS with Laravel and Vue - Part 6: Adding Realtime Comments

In the previous part of this series, we finished building the backend of the application using Vue. We were able to add the create and update component, which is used for creating a new post and updating an existing post.

Here’s a screen recording of what we have been able to achieve:

In this final part of the series, we will be adding support for comments. We will also ensure that the comments on each post are updated in realtime, so a user doesn’t have to refresh the page to see new comments.

When we are done, our application will have new features and will work like this:

The source code for this project is available here on Github.

Prerequisites

To follow along with this series, a few things are required:

  • A Pusher account. Sign up here.
  • Basic knowledge of PHP.
  • Basic knowledge of the Laravel framework.
  • Basic knowledge of JavaScript (ES6 syntax).
  • Basic knowledge of Vue.

Adding comments to the backend

When we were creating the API, we did not add the support for comments to the post resource, so we will have to do so now. Open the API project in your text editor as we will be modifying the project a little.

The first thing we want to do is create a model, controller, and a migration for the comment resource. To do this, open your terminal and cd to the project directory and run the following command:

<pre class="ql-syntax" spellcheck="false"> $ php artisan make:model Comment -mc

</pre>

The command above will create a model called Comment, a controller called CommentController, and a migration file in the database/migrations directory.


Updating the comments migration file

To update the comments migration navigate to the database/migrations folder and find the newly created migration file for the Comment model. Let’s update the up() method in the file:

<pre class="ql-syntax" spellcheck="false"> // File: ./database/migrations/*_create_comments_table.php
public function up()
{
Schema::create('comments', function (Blueprint $table) {
$table->increments('id');
$table->timestamps();
$table->integer('user_id')->unsigned();
$table->integer('post_id')->unsigned();
$table->text('body');
});
}
</pre>

We included user_id and post_id fields because we intend to create a link between the comments, users, and posts. The body field will contain the actual comment.


Defining the relationships among the Comment, User, and Post models

In this application, a comment will belong to a user and a post because a user can make a comment on a specific post, so we need to define the relationship that ties everything up.

Open the User model and include this method:

<pre class="ql-syntax" spellcheck="false"> // File: ./app/User.php
public function comments()
{
return $this->hasMany(Comment::class);
}
</pre>

This is a relationship that simply says that a user can have many comments. Now let’s define the same relationship on the Post model. Open the Post.php file and include this method:

<pre class="ql-syntax" spellcheck="false"> // File: ./app/Post.php
public function comments()
{
return $this->hasMany(Comment::class);
}
</pre>

Finally, we will include two methods in the Comment model to complete the second half of the relationships we defined in the User and Post models.

Open the app/Comment.php file and include these methods:

<pre class="ql-syntax" spellcheck="false"> // File: ./app/Comment.php
public function user()
{
return $this->belongsTo(User::class);
}

public function post()
{
    return $this-&gt;belongsTo(Post::class);
}

</pre>

Since we want to be able to mass assign data to specific fields of a comment instance during comment creation, we will include this array of permitted assignments in the app/Comment.php file:

<pre class="ql-syntax" spellcheck="false"> protected $fillable = ['user_id', 'post_id', 'body'];
</pre>

We can now run our database migration for our comments:

<pre class="ql-syntax" spellcheck="false"> $ php artisan migrate

</pre>

Configuring Laravel to broadcast events using Pusher

We already said that the comments will have a realtime functionality and we will be building this using Pusher, so we need to enable Laravel’s event broadcasting feature.

Open the config/app.php file and uncomment the following line in the providers array:

<pre class="ql-syntax" spellcheck="false"> App\Providers\BroadcastServiceProvider

</pre>

Next, we need to configure the broadcast driver in the .env file:

<pre class="ql-syntax" spellcheck="false"> BROADCAST_DRIVER=pusher

</pre>

Let’s pull in the Pusher PHP SDK using composer:

<pre class="ql-syntax" spellcheck="false"> $ composer require pusher/pusher-php-server

</pre>

Configuring Pusher

For us to use Pusher in this application, it is a prerequisite that you have a Pusher account. You can create a free Pusher account here then login to your dashboard and create an app.

Once you have created an app, we will use the app details to configure pusher in the .env file:

<pre class="ql-syntax" spellcheck="false"> PUSHER_APP_ID=xxxxxx
PUSHER_APP_KEY=xxxxxxxxxxxxxxxxxxxx
PUSHER_APP_SECRET=xxxxxxxxxxxxxxxxxxxx
PUSHER_APP_CLUSTER=xx

</pre>

Update the Pusher keys with the app credentials provided for you under the Keys section on the Overview tab on the Pusher dashboard.


Broadcasting an event for when a new comment is sent

To make the comment update realtime, we have to broadcast an event based on the comment creation activity. We will create a new event and call it CommentSent. It is to be fired when there is a successful creation of a new comment.

Run command in your terminal:

<pre class="ql-syntax" spellcheck="false"> php artisan make:event CommentSent

</pre>

There will be a newly created file in the app\Events directory, open the CommentSent.php file and ensure that it implements the ShouldBroadcast interface.

Open and replace the file with the following code:

<pre class="ql-syntax" spellcheck="false"> // File: ./app/Events/CommentSent.php
<?php

namespace App\Events;

use App\Comment;
use App\User;
use Illuminate\Broadcasting\Channel;
use Illuminate\Queue\SerializesModels;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;

class CommentSent implements ShouldBroadcast
{
    use Dispatchable, InteractsWithSockets, SerializesModels;

    public $user;

    public $comment;

    public function __construct(User $user, Comment $comment)
    {
        $this-&gt;user = $user;

        $this-&gt;comment = $comment;
    }

    public function broadcastOn()
    {
        return new PrivateChannel('comment');
    }
}

</pre>

In the code above, we created two public properties, user and comment, to hold the data that will be passed to the channel we are broadcasting on. We also created a private channel called comment. We are using a private channel so that only authenticated clients can subscribe to the channel.


Defining the routes for handling operations on a comment

We created a controller for the comment model earlier but we haven’t defined the web routes that will redirect requests to be handled by that controller.

Open the routes/web.php file and include the code below:

<pre class="ql-syntax" spellcheck="false"> // File: ./routes/web.php
Route::get('/{post}/comments', '[email protected]');
Route::post('/{post}/comments', '[email protected]');
</pre>

Setting up the action methods in the CommentController

We need to include two methods in the CommentController.php file, these methods will be responsible for storing and retrieving methods. In the store() method, we will also be broadcasting an event when a new comment is created.

Open the CommentController.php file and replace its contents with the code below:

<pre class="ql-syntax" spellcheck="false"> // File: ./app/Http/Controllers/CommentController.php
<?php

namespace App\Http\Controllers;

use App\Comment;
use App\Events\CommentSent;
use App\Post;
use Illuminate\Http\Request;

class CommentController extends Controller
{
    public function store(Post $post)
    {
        $this-&gt;validate(request(), [
            'body' =&gt; 'required',
        ]);

        $user = auth()-&gt;user();

        $comment = Comment::create([
            'user_id' =&gt; $user-&gt;id,
            'post_id' =&gt; $post-&gt;id,
            'body' =&gt; request('body'),
        ]);

        broadcast(new CommentSent($user, $comment))-&gt;toOthers();

        return ['status' =&gt; 'Message Sent!'];
    }

    public function index(Post $post)
    {
        return $post-&gt;comments()-&gt;with('user')-&gt;get();
    }
}

</pre>

In the store method above, we are validating then creating a new post comment. After the comment has been created, we broadcast the CommentSent event to other clients so they can update their comments list in realtime.

In the index method we just return the comments belonging to a post along with the user that made the comment.


Adding a layer of authentication

Let’s add a layer of authentication that ensures that only authenticated users can listen on the private comment channel we created.

Add the following code to the routes/channels.php file:

<pre class="ql-syntax" spellcheck="false"> // File: ./routes/channels.php
Broadcast::channel('comment', function ($user) {
return auth()->check();
});
</pre>

Adding comments to the frontend

In the second article of this series, we created the view for the single post landing page in the single.blade.php file, but we didn’t add the comments functionality. We are going to add it now. We will be using Vue to build the comments for this application so the first thing we will do is include Vue in the frontend of our application.

Open the master layout template and include Vue to its <head> tag. Just before the <title> tag appears in the master.blade.php file, include this snippet:

<pre class="ql-syntax" spellcheck="false"> <!-- File: ./resources/views/layouts/master.blade.php -->
<meta name="csrf-token" content="{{ csrf_token() }}">
<script src="{{ asset('js/app.js') }}" defer></script>
</pre>

The csrf_token() is there so that users cannot forge requests in our application. All our requests will pick the randomly generated csrf-token and use that to make requests.

Related: CSRF in Laravel: how VerifyCsrfToken works and how to prevent attacks

Now the next thing we want to do is update the resources/assets/js/app.js file so that it includes a template for the comments view.

Open the file and replace its contents with the code below:

<pre class="ql-syntax" spellcheck="false"> require('./bootstrap');

import Vue          from 'vue'
import VueRouter    from 'vue-router'
import Homepage from './components/Homepage'
import Create   from './components/Create'
import Read     from './components/Read'
import Update   from './components/Update'
import Comments from './components/Comments'

Vue.use(VueRouter)

const router = new VueRouter({
    mode: 'history',
    routes: [
        {
            path: '/admin/dashboard',
            name: 'read',
            component: Read,
            props: true
        },
        {
            path: '/admin/create',
            name: 'create',
            component: Create,
            props: true
        },
        {
            path: '/admin/update',
            name: 'update',
            component: Update,
            props: true
        },
    ],
});

const app = new Vue({
    el: '#app',
    components: { Homepage, Comments },
    router,
});

</pre>

Above we imported the Comment component and then we added it to the list of components in the applications Vue instance.

Now create a Comments.vue file in the resources/assets/js/components directory. This is where all the code for our comment view will go. We will populate this file later on.


Installing Pusher and Laravel Echo

For us to be able to use Pusher and subscribe to events on the frontend, we need to pull in both Pusher and Laravel Echo. We will do so by running this command:

<pre class="ql-syntax" spellcheck="false"> $ npm install --save laravel-echo pusher-js

</pre>

Laravel Echo is a JavaScript library that makes it easy to subscribe to channels and listen for events broadcast by Laravel.

Now let’s configure Laravel Echo to work in our application. In the resources/assets/js/bootstrap.js file, find and uncomment this snippet of code:

<pre class="ql-syntax" spellcheck="false"> import Echo from 'laravel-echo'

window.Pusher = require('pusher-js');

window.Echo = new Echo({
     broadcaster: 'pusher',
     key: process.env.MIX_PUSHER_APP_KEY,
     cluster: process.env.MIX_PUSHER_APP_CLUSTER,
     encrypted: true
});

</pre>

The key and cluster will pull the keys from your .env file so no need to enter them manually again.

Now let’s import the Comments component into the single.blade.php file and pass along the required the props.

Open the single.blade.php file and replace its contents with the code below:

<pre class="ql-syntax" spellcheck="false"> {{-- File: ./resources/views/single.blade.php --}}
@extends('layouts.master')

@section('content')
&lt;div class="container"&gt;
  &lt;div class="row"&gt;
    &lt;div class="col-lg-10 mx-auto"&gt;
      &lt;br&gt;
      &lt;h3 class="mt-4"&gt;
        {{ $post-&gt;title }} 
        &lt;span class="lead"&gt;by &lt;a href="#"&gt;{{ $post-&gt;user-&gt;name }}&lt;/a&gt;&lt;/span&gt;
      &lt;/h3&gt;
      &lt;hr&gt;
      &lt;p&gt;Posted {{ $post-&gt;created_at-&gt;diffForHumans() }}&lt;/p&gt;
      &lt;hr&gt;
      &lt;img class="img-fluid rounded" src="{!! !empty($post-&gt;image) ? '/uploads/posts/' . $post-&gt;image : 'http://placehold.it/750x300' !!}" alt=""&gt;
      &lt;hr&gt;
      &lt;div&gt;
        &lt;p&gt;{{ $post-&gt;body }}&lt;/p&gt;
        &lt;hr&gt;
        &lt;br&gt;
      &lt;/div&gt;

      @auth
      &lt;Comments
          :post-id='@json($post-&gt;id)' 
          :user-name='@json(auth()-&gt;user()-&gt;name)'&gt;
      &lt;/Comments&gt;
      @endauth
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;
@endsection

</pre>

Building the comments view

Open the Comments.vue file and add the following markup template below:

<pre class="ql-syntax" spellcheck="false"> <template>
<div class="card my-4">
<h5 class="card-header">Leave a Comment:</h5>
<div class="card-body">
<form>
<div class="form-group">
<textarea ref="body" class="form-control" rows="3"></textarea>
</div>
<button type="submit" @click.prevent="addComment" class="btn btn-primary">
Submit
</button>
</form>
</div>
<p class="border p-3" v-for="comment in comments">
<strong>{{ comment.user.name }}</strong>:
<span>{{ comment.body }}</span>
</p>
</div>
</template>
</pre>

Now, we’ll add a script that defines two methods:

  1. fetchComments() - this will fetch all the existing comments when the component is created.
  2. addComment() - this will add a new comment by hitting the backend server. It will also trigger a new event that will be broadcast so all clients receive them in realtime.

In the same file, add the following below the closing template tag:

<pre class="ql-syntax" spellcheck="false"> <script>
export default {
props: {
userName: {
type: String,
required: true
},
postId: {
type: Number,
required: true
}
},
data() {
return {
comments: []
};
},

  created() {
    this.fetchComments();

    Echo.private("comment").listen("CommentSent", e =&gt; {
        this.comments.push({
          user: {name: e.user.name},
          body: e.comment.body,
        });
    });
  },

  methods: {
    fetchComments() {
      axios.get("/" + this.postId + "/comments").then(response =&gt; {
        this.comments = response.data;
      });
    },

    addComment() {
      let body = this.$refs.body.value;
      axios.post("/" + this.postId + "/comments", { body }).then(response =&gt; {
        this.comments.push({
          user: {name: this.userName},
          body: this.$refs.body.value
        });
        this.$refs.body.value = "";
      });
    }
  }
};
&lt;/script&gt;

</pre>

In the created() method above, we first made a call to the fetchComments() method, then we created a listener to the private comment channel using Laravel Echo. Once this listener is triggered, the comments property is updated.


Testing the application

Now let’s test the application to see if it is working as intended. Before running the application, we need to refresh our database so as to revert any changes. To do this, run the command below in your terminal:

<pre class="ql-syntax" spellcheck="false"> $ php artisan migrate:fresh --seed

</pre>

Next, let’s build the application so that all the changes will be compiled and included as a part of the JavaScript file. To do this, run the following command on your terminal:

<pre class="ql-syntax" spellcheck="false"> $ npm run dev

</pre>

Finally, let’s serve the application using this command:

<pre class="ql-syntax" spellcheck="false"> $ php artisan serve

</pre>

To test that our application works visit the application URL http://localhost:8000 on two separate browser windows, we will log in to our application on each of the windows as a different user.

We will finally make a comment on the same post on each of the browser windows and check that it updates in realtime on the other window:


Conclusion

In this final tutorial of this series, we created the comments feature of the CMS and also made it realtime. We were able to accomplish the realtime functionality using Pusher.

In this entire series, we learned how to build a CMS using Laravel and Vue.

The source code for this article series is available here on Github.


Learn More

Build a Basic CRUD App with Laravel and Vue

Fullstack Vue App with Node, Express and MongoDB

Build a Simple CRUD App with Spring Boot and Vue.js

Build a Basic CRUD App with Laravel and Angular

Build a Basic CRUD App with Laravel and React

PHP Programming Language - PHP Tutorial for Beginners

Vuejs 2 Authentication Tutorial

Vue Authentication And Route Handling Using Vue-router

Vue JS 2 - The Complete Guide (incl. Vue Router & Vuex)

Nuxt.js - Vue.js on Steroids

MEVP Stack Vue JS 2 Course: MySQL + Express.js + Vue.js +PHP

Build Web Apps with Vue JS 2 & Firebase

Originally published by Neo Ighodaro at https://pusher.com

Build RESTful API In Laravel 5.8 Example

Build RESTful API In Laravel 5.8 Example


If you want to create web services with php than i will must suggest to use laravel 5.8 to create apis because laravel provide structure with authentication using passport. Based on structure it will become a very easily way to create rest apis.

Just Few days ago, laravel released it's new version as laravel 5.8. As we know laravel is a more popular because of security feature. So many of the developer choose laravel to create rest api for mobile app developing. Yes Web services is a very important when you create web and mobile developing, because you can create same database and work with same data.

Follow bellow few steps to create restful api example in laravel 5.8 app.

Step 1: Download Laravel 5.8

I am going to explain step by step from scratch so, we need to get fresh Laravel 5.8 application using bellow command, So open your terminal OR command prompt and run bellow command:

<pre class="ql-syntax" spellcheck="false">composer create-project --prefer-dist laravel/laravel blog </pre>

Step 2: Install Passport

In this step we need to install passport via the Composer package manager, so one your terminal and fire bellow command:

<pre class="ql-syntax" spellcheck="false">composer require laravel/passport </pre>

After successfully install package, we require to get default migration for create new passport tables in our database. so let's run bellow command.

<pre class="ql-syntax" spellcheck="false">php artisan migrate </pre>

Next, we need to install passport using command, Using passport:install command, it will create token keys for security. So let's run bellow command:

<pre class="ql-syntax" spellcheck="false">php artisan passport:install </pre>

Step 3: Passport Configuration

In this step, we have to configuration on three place model, service provider and auth config file. So you have to just following change on that file.

In model we added HasApiTokens class of Passport,

In AuthServiceProvider we added "Passport::routes()",

In auth.php, we added api auth configuration.

app/User.php

<pre class="ql-syntax" spellcheck="false"><?php

namespace App;

use Illuminate\Notifications\Notifiable;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Laravel\Passport\HasApiTokens;
use Illuminate\Foundation\Auth\User as Authenticatable;

class User extends Authenticatable implements MustVerifyEmail
{
use HasApiTokens, Notifiable;

/**
 * The attributes that are mass assignable.
 *
 * @var array
 */
protected $fillable = [
    'name', 'email', 'password',
];

/**
 * The attributes that should be hidden for arrays.
 *
 * @var array
 */
protected $hidden = [
    'password', 'remember_token',
];

}
</pre>

app/Providers/AuthServiceProvider.php

<pre class="ql-syntax" spellcheck="false"><?php

namespace App\Providers;

use Laravel\Passport\Passport;
use Illuminate\Support\Facades\Gate;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;

class AuthServiceProvider extends ServiceProvider
{
/**
* The policy mappings for the application.
*
* @var array
*/
protected $policies = [
'App\Model' => 'App\Policies\ModelPolicy',
];

/**
 * Register any authentication / authorization services.
 *
 * @return void
 */
public function boot()
{
    $this-&gt;registerPolicies();

    Passport::routes();
}

}
</pre>

config/auth.php

<pre class="ql-syntax" spellcheck="false"><?php

return [
.....
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'api' => [
'driver' => 'passport',
'provider' => 'users',
],
],
.....
]
</pre>

Step 4: Add Product Table and Model

next, we require to create migration for posts table using Laravel 5.8 php artisan command, so first fire bellow command:

<pre class="ql-syntax" spellcheck="false">php artisan make:migration create_products_table
</pre>

After this command you will find one file in following path database/migrations and you have to put bellow code in your migration file for create products table.

<pre class="ql-syntax" spellcheck="false"><?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateProductsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('products', function (Blueprint $table) {
$table->increments('id');
$table->string('name');
$table->text('detail');
$table->timestamps();
});
}

/**
 * Reverse the migrations.
 *
 * @return void
 */
public function down()
{
    Schema::dropIfExists('products');
}

}
</pre>

After create migration we need to run above migration by following command:

<pre class="ql-syntax" spellcheck="false">php artisan migrate
</pre>

After create "products" table you should create Product model for products, so first create file in this path app/Product.php and put bellow content in item.php file:


app/Product.php

<pre class="ql-syntax" spellcheck="false"><?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Product extends Model
{
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'name', 'detail'
];
}
</pre>

Step 5: Create API Routes

In this step, we will create api routes. Laravel provide api.php file for write web services route. So, let's add new route on that file.

routes/api.php

<pre class="ql-syntax" spellcheck="false"><?php

/*
|--------------------------------------------------------------------------

API Routes
Here is where you can register API routes for your application. These
routes are loaded by the RouteServiceProvider within a group which
is assigned the "api" middleware group. Enjoy building your API!

*/

Route::post('register', 'API\[email protected]');

Route::middleware('auth:api')->group( function () {
Route::resource('products', 'API\ProductController');
});
</pre>

Step 6: Create Controller Files

in next step, now we have create new controller as BaseController, ProductController and RegisterController, i created new folder "API" in Controllers folder because we will make alone APIs controller, So let's create both controller:

app/Http/Controllers/API/BaseController.php

<pre class="ql-syntax" spellcheck="false"><?php

namespace App\Http\Controllers\API;

use Illuminate\Http\Request;
use App\Http\Controllers\Controller as Controller;

class BaseController extends Controller
{
/**
* success response method.
*
* @return \Illuminate\Http\Response
*/
public function sendResponse($result, $message)
{
$response = [
'success' => true,
'data' => $result,
'message' => $message,
];

    return response()-&gt;json($response, 200);
}

/**
 * return error response.
 *
 * @return \Illuminate\Http\Response
 */
public function sendError($error, $errorMessages = [], $code = 404)
{
	$response = [
        'success' =&gt; false,
        'message' =&gt; $error,
    ];

    if(!empty($errorMessages)){
        $response['data'] = $errorMessages;
    }

    return response()-&gt;json($response, $code);
}

}
</pre>

app/Http/Controllers/API/ProductController.php

<pre class="ql-syntax" spellcheck="false"><?php

namespace App\Http\Controllers\API;

use Illuminate\Http\Request;
use App\Http\Controllers\API\BaseController as BaseController;
use App\Product;
use Validator;

class ProductController extends BaseController
{
/**
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response
*/
public function index()
{
$products = Product::all();

    return $this-&gt;sendResponse($products-&gt;toArray(), 'Products retrieved successfully.');
}

/**
 * Store a newly created resource in storage.
 *
 * @param  \Illuminate\Http\Request  $request
 * @return \Illuminate\Http\Response
 */
public function store(Request $request)
{
    $input = $request-&gt;all();

    $validator = Validator::make($input, [
        'name' =&gt; 'required',
        'detail' =&gt; 'required'
    ]);

    if($validator-&gt;fails()){
        return $this-&gt;sendError('Validation Error.', $validator-&gt;errors());       
    }

    $product = Product::create($input);

    return $this-&gt;sendResponse($product-&gt;toArray(), 'Product created successfully.');
}

/**
 * Display the specified resource.
 *
 * @param  int  $id
 * @return \Illuminate\Http\Response
 */
public function show($id)
{
    $product = Product::find($id);

    if (is_null($product)) {
        return $this-&gt;sendError('Product not found.');
    }

    return $this-&gt;sendResponse($product-&gt;toArray(), 'Product retrieved successfully.');
}

/**
 * Update the specified resource in storage.
 *
 * @param  \Illuminate\Http\Request  $request
 * @param  int  $id
 * @return \Illuminate\Http\Response
 */
public function update(Request $request, Product $product)
{
    $input = $request-&gt;all();

    $validator = Validator::make($input, [
        'name' =&gt; 'required',
        'detail' =&gt; 'required'
    ]);

    if($validator-&gt;fails()){
        return $this-&gt;sendError('Validation Error.', $validator-&gt;errors());       
    }

    $product-&gt;name = $input['name'];
    $product-&gt;detail = $input['detail'];
    $product-&gt;save();

    return $this-&gt;sendResponse($product-&gt;toArray(), 'Product updated successfully.');
}

/**
 * Remove the specified resource from storage.
 *
 * @param  int  $id
 * @return \Illuminate\Http\Response
 */
public function destroy(Product $product)
{
    $product-&gt;delete();

    return $this-&gt;sendResponse($product-&gt;toArray(), 'Product deleted successfully.');
}

}
</pre>

app/Http/Controllers/API/RegisterController.php

<pre class="ql-syntax" spellcheck="false"><?php

namespace App\Http\Controllers\API;

use Illuminate\Http\Request;
use App\Http\Controllers\API\BaseController as BaseController;
use App\User;
use Illuminate\Support\Facades\Auth;
use Validator;

class RegisterController extends BaseController
{
/**
* Register api
*
* @return \Illuminate\Http\Response
*/
public function register(Request $request)
{
$validator = Validator::make($request->all(), [
'name' => 'required',
'email' => 'required|email',
'password' => 'required',
'c_password' => 'required|same:password',
]);

    if($validator-&gt;fails()){
        return $this-&gt;sendError('Validation Error.', $validator-&gt;errors());       
    }

    $input = $request-&gt;all();
    $input['password'] = bcrypt($input['password']);
    $user = User::create($input);
    $success['token'] =  $user-&gt;createToken('MyApp')-&gt;accessToken;
    $success['name'] =  $user-&gt;name;

    return $this-&gt;sendResponse($success, 'User register successfully.');
}

}
</pre>

Now we are ready to to run full restful api and also passport api in laravel. so let's run our example so run bellow command for quick run:

<pre class="ql-syntax" spellcheck="false">php artisan serve
</pre>

make sure in details api we will use following headers as listed bellow:

<pre class="ql-syntax" spellcheck="false">'headers' => [
'Accept' => 'application/json',
'Authorization' => 'Bearer '.$accessToken,
]
</pre>

Here is Routes URL with Verb:

1) Login: Verb:GET, URL:http://localhost:8000/oauth/token

2) Register: Verb:GET, URL:http://localhost:8000/api/register

3) List: Verb:GET, URL:http://localhost:8000/api/products

4) Create: Verb:POST, URL:http://localhost:8000/api/products

5) Show: Verb:GET, URL:http://localhost:8000/api/products/{id}

6) Update: Verb:PUT, URL:http://localhost:8000/api/products/{id}

7) Delete: Verb:DELETE, URL:http://localhost:8000/api/products/{id}

Now simply you can run above listed url like as bellow screen shot:

Login API:

Register API:

Product List API:

Product Create API:

Product Show API:

Product Update API:

Product Delete API:

I hope it can help you...

Thanks for reading ❤

If you liked this post, share it with all of your programming buddies!

Follow me on Facebook | Twitter

Learn More

PHP with Laravel for beginners - Become a Master in Laravel

Projects in Laravel: Learn Laravel Building 10 Projects

Laravel for RESTful: Build Your RESTful API with Laravel

Fullstack Web Development With Laravel and Vue.js

Creating RESTful APIs with NodeJS and MongoDB Tutorial

Developing RESTful APIs with Lumen (A PHP Micro-framework)

Build a Simple REST API in PHP

Node.js and Express Tutorial: Building and Securing RESTful APIs

Building a Vue SPA With Laravel

Build a CMS with Laravel and Vue