How To Develop Nuxt CRUD App using a REST API

How To Develop Nuxt CRUD App using a REST API

Avatar photoPosted by

Hi there, today we will be developing a Nuxt CRUD app using a REST API. The four basic operations in any type of programming are CRUD. CRUD is an acronym for CREATE, READ, UPDATE, DELETE.

Nuxt is a framework for building Vue.js applications. It provides a higher-level abstraction for developing Vue applications with features that enhance the development process.

In this tutorial, we will be using the API available that is made by Binaryboxtus. you can check it out here.

Prerequisite:

  • node >= v18.0.0
  • npx

Step 1: Create A Nuxt Project

First, select a folder that you want the nuxt project to be created then execute this command on Terminal or CMD :

npx nuxi@latest init nuxt-crud-app

Step 2: Install packages

After creating a fresh nuxt project, go to the nuxt project folder and install these packages:

  • bootstrap – a package of bootstrap framework
  • sweetalert2 -a package for pop-up boxes
  • axios – a promised-based HTTP library that is used to request to a server or API.
npm i bootstrap
npm i sweetalert2
npm i axios

After installing the packages, add bootstrap and the runtime config in nuxt.config.ts. We will add API key and API url in the runtime config. You can get the API key and API url here. Click the “Reveal Key” button and copy the API key:

image 31 Binaryboxtuts

nuxt.config.ts

// https://nuxt.com/docs/api/configuration/nuxt-config
export default defineNuxtConfig({
  devtools: { enabled: true },
  css: [
    'bootstrap/dist/css/bootstrap.css',
  ],
  runtimeConfig: {
    public: {
      apiUrl:'https://mock-api.binaryboxtuts.com',
      apiKey:'put_the_api_key_here'
    }
  },
})

Step 3: Create Services And Plugins

Create a folder named plugins and inside it create a file named axiosApiClient.ts. Then add these lines of code:

plugins\axiosApiClient.ts

import axios from 'axios';
 
export default defineNuxtPlugin((nuxtApp) => {
    const runctimeConfig = useRuntimeConfig()
    const axiosApiClient = axios.create()
    axiosApiClient.defaults.baseURL = runctimeConfig.public.apiUrl
    axiosApiClient.interceptors.request.use(function (config) {
        config.headers['X-Binarybox-Api-Key'] = runctimeConfig.public.apiKey
        return config;
    });
    return {
        provide: {
            axiosApiClient: axiosApiClient,
        },
    };
});
  

Create a folder named services and inside it create a file named projectService.js. Then add these lines of code:

services\projectService.js

export const getProjects = () =>{
    return useNuxtApp().$axiosApiClient.get('/api/projects')
}   
 
export const getProject = (id) =>{
    return useNuxtApp().$axiosApiClient.get(`/api/projects/${id}`)
}   
 
export const createProject = (project) =>{
    return useNuxtApp().$axiosApiClient.post('/api/projects', project)
}   
 
export const updateProject = (id, project) =>{
    return useNuxtApp().$axiosApiClient.patch(`/api/projects/${id}`, project)
}
 
export const deleteProject = (id) =>{
    return useNuxtApp().$axiosApiClient.delete(`/api/projects/${id}`)
}

Step 4: Create Layouts and Pages

We will now create the pages and layouts, refer to the image below for the file structure.

image 32 Binaryboxtuts

Let’s update the app.vue file – Let’s change all the code in it:

app.vue

<template>
  <NuxtLayout>
    <Head>
      <title>NUXT CRUD APP</title>
    </Head>
    <NuxtPage />
  </NuxtLayout>
</template>

Create a new folder and name it layouts and pages. and after that let’s create the layout:

layouts\default.vue

<template>
  <div class="container">
    <slot/>
  </div>
</template>

We will now create the following pages:

pages\index.vue

<template>
  <div class="container">
      <h2 class="text-center mt-5 mb-3">Project Manager</h2>
      <div class="card">
          <div class="card-header">
              <NuxtLink to="/create"
                  class="btn btn-outline-primary"
                  >Create New Project
              </NuxtLink>
          </div>
          <div class="card-body">
        
              <table class="table table-bordered">
                  <thead>
                      <tr>
                          <th>Name</th>
                          <th>Description</th>
                          <th width="240px">Action</th>
                      </tr>
                  </thead>
                  <tbody>
                        
                      <tr v-for="project in projects" :key="project.id">
                          <td>{{project.name}}</td>
                          <td>{{project.description}}</td>
                          <td>
                              <NuxtLink :to="`/show/${project.id}`" class="btn btn-outline-info mx-1">Show</NuxtLink>
                              <NuxtLink :to="`/edit/${project.id}`" class="btn btn-outline-success mx-1">Edit</NuxtLink>
                              <button 
                                  @click="handleDelete(project.id)"
                                  className="btn btn-outline-danger mx-1">
                                  Delete
                              </button>
                          </td>
                      </tr>
                            
                  </tbody>
              </table>
          </div>
      </div>
  </div>
</template>
 
<script>


import { deleteProject, getProjects } from '~/services/projectService'
import Swal from 'sweetalert2'

export default {

  data() {
    return {
      projects:[]
    };
  },
  
  created(){
    this.fetchProjectList()
  },

  methods:{
    fetchProjectList() {       
      getProjects()
        .then(response => {
            this.projects = response.data;
            return response
        })
        .catch(error => {
          return error
      });

    },

    handleDelete(id){
      Swal.fire({
          title: 'Are you sure?',
          text: "You won't be able to revert this!",
          icon: 'warning',
          showCancelButton: true,
          confirmButtonColor: '#3085d6',
          cancelButtonColor: '#d33',
          confirmButtonText: 'Yes, delete it!'
        }).then((result) => {
        if (result.isConfirmed) {
          deleteProject(id)
            .then( response => {
                Swal.fire({
                    icon: 'success',
                    title: 'Project deleted successfully!',
                    showConfirmButton: false,
                    timer: 1500
                })
                this.fetchProjectList();
                return response
            })
            .catch(error => {
                Swal.fire({
                      icon: 'error',
                    title: 'An Error Occured!',
                    showConfirmButton: false,
                    timer: 1500
                })
                return error
            });
        }
      })
    }
  

  }
};
</script>

pages\create.vue

<template>
  <h2 class="text-center mt-5 mb-3">Create New Project</h2>
  <div class="card">
      <div class="card-header">
          <NuxtLink 
              class="btn btn-outline-info float-right"
              to="/">View All Projects
          </NuxtLink>
      </div>
      <div class="card-body">
          <form>
              <div class="form-group">
                  <label htmlFor="name">Name</label>
                  <input 
                      v-model="project.name"
                      type="text"
                      class="form-control"
                      id="name"
                      name="name"/>
              </div>
              <div class="form-group">
                  <label htmlFor="description">Description</label>
                  <textarea 
                      v-model="project.description"
                      class="form-control"
                      id="description"
                      rows="3"
                      name="description"></textarea>
              </div>
              <button 
                  @click="handleSave()"
                  :disabled="isSaving"
                  type="button"
                  class="btn btn-outline-primary mt-3">
                  Save Project
              </button>
          </form>
      </div>
  </div>
</template>
 
<script>

import Swal from 'sweetalert2'
import { createProject } from '~/services/projectService';
 
export default {
  data() {
    return {
      project: {
        name: '',
        description: '',
      },
      isSaving:false,
    };
  },
  methods: {
    handleSave() {
        this.isSaving = true
        createProject(this.project)
          .then(response => {
            Swal.fire({
                icon: 'success',
                title: 'Project saved successfully!',
                showConfirmButton: false,
                timer: 1500
            })
            this.isSaving = false
            this.project.name = ""
            this.project.description = ""
            return response
          })
          .catch(error => {
            this.isSaving = false
            Swal.fire({
                icon: 'error',
                title: 'An Error Occured!',
                showConfirmButton: false,
                timer: 1500
            })
            return error
          });
    },
  },
};
</script>

pages\edit\[id].vue

<template>
  <h2 class="text-center mt-5 mb-3">Edit Project</h2>
  <div class="card">
      <div class="card-header">
          <router-link 
              class="btn btn-outline-info float-right"
              to="/">View All Projects
          </router-link>
      </div>
      <div class="card-body">
          <form>
              <div class="form-group">
                  <label htmlFor="name">Name</label>
                  <input 
                      v-model="project.name"
                      type="text"
                      class="form-control"
                      id="name"
                      name="name"/>
              </div>
              <div class="form-group">
                  <label htmlFor="description">Description</label>
                  <textarea 
                      v-model="project.description"
                      class="form-control"
                      id="description"
                      rows="3"
                      name="description"></textarea>
              </div>
              <button 
                  @click="handleSave()"
                  :disabled="isSaving"
                  type="button"
                  class="btn btn-outline-primary mt-3">
                  Save Project
              </button>
          </form>
      </div>
  </div>
</template>
 
<script>

import Swal from 'sweetalert2'
import { getProject, updateProject } from '~/services/projectService';

export default {

  data() {
    return {
      project: {
        name: '',
        description: '',
      },
      isSaving:false,
    };
  },
  created() {
    const id = this.$route.params.id;
    getProject(id)
    .then(response => {
        let projectInfo = response.data
        this.project.name = projectInfo.name
        this.project.description = projectInfo.description
        return response
    })
    .catch(error => {
        Swal.fire({
            icon: 'error',
            title: 'An Error Occured!',
            showConfirmButton: false,
            timer: 1500
        })
        return error
    })
  },
  methods: {
    handleSave() {
        this.isSaving = true
        const id = this.$route.params.id;

        updateProject(id, this.project)
          .then(response => {
            Swal.fire({
                icon: 'success',
                title: 'Project updated successfully!',
                showConfirmButton: false,
                timer: 1500
            })
            this.isSaving = false
            this.project.name = ""
            this.project.description = ""
            return response
          })
          .catch(error => {
            this.isSaving = false
            Swal.fire({
                icon: 'error',
                title: 'An Error Occured!',
                showConfirmButton: false,
                timer: 1500
            })
            return error
          });
    },
  },
};
</script>

pages\show\[id].vue

<template>
  <h2 class="text-center mt-5 mb-3">Show Project</h2>
  <div class="card">
      <div class="card-header">
          <router-link 
              class="btn btn-outline-info float-right"
              to="/">View All Projects
          </router-link>
      </div>
      <div class="card-body">
          <b className="text-muted">Name:</b>
          <p>{{project.name}}</p>
          <b className="text-muted">Description:</b>
          <p>{{project.description}}</p>
      </div>
  </div>
</template>
 
<script>

import Swal from 'sweetalert2'
import { getProject } from '~/services/projectService';
 
export default {
  data() {
    return {
      project: {
        name: '',
        description: '',
      },
      isSaving:false,
    };
  },
  created() {
    const id = this.$route.params.id;
    getProject(id)
    .then(response => {
        let projectInfo = response.data
        this.project.name = projectInfo.name
        this.project.description = projectInfo.description
        return response
    })
    .catch(error => {
        Swal.fire({
            icon: 'error',
            title: 'An Error Occured!',
            showConfirmButton: false,
            timer: 1500
        })
        return error
    })
  },
  methods: {
     
  },
};
</script>

Step 5: Run the app

We’re all done, what is left is to run the app.

npm run dev

Open this URL:

http://localhost:3000/

Screenshots:

Index Page

laravel-8-react-crud-create-image

Create Page

laravel-8-react-crud-create-image

Edit Page

laravel-8-react-crud-show-image

Show Page

laravel-8-react-crud-show-image