Building an Animal Adoption Site with Node and Vue - Part 1

Publikováno: 8.7.2019

Today we're going to build an application that showcases animals that are available for adoption! This will be a two part blog series, so in this portion we'll just learn how to se...

Celý článek

Today we're going to build an application that showcases animals that are available for adoption! This will be a two part blog series, so in this portion we'll just learn how to setup our application and get some mock data flowing from the backend to the frontend. Let's get started!

How We'll Do It

There will be a few moving parts in our animal adoption app, so let's first simplify it and lay out exactly what we're going to need:

  • PetFinder API to pull animals available for adoption
  • Node.js backend to make the API calls
  • Vue.js frontend to display the results

Create the Backend

We're going to be separating our frontend from our backend, so to make that easier to follow we'll just create two separate folders in the main project folder to hold the frontend code and backend code.

First create a folder that's hold our entire project and then cd into that folder. Then create a vue-client folder and an express-server folder.


mkdir animal-adoption-site
cd animal-adoption-site

mkdir express-server
mkdir vue-client

Install Node.js

We'll be using Node and npm for this tutorial, so let's just go ahead and get that setup now if it's not already on your machine. You can download Node.js here.

Now let's enter into the express-server folder and initialize a Node project.


cd express-server
npm init

This will walk through the creation of your package.json file. The defaults are fine for our use, so just press enter when prompted.

Our package.json file will help manage our dependencies. Let's add some now.

Install Dependencies

Here's what we'll need for our project:

  • Express -- This is a lightweight framework for Node. It's not mandatory to use in a Node app, but it will make most tasks a lot easier.
  • Nodemon -- Nodemon will keep our server running and automatically refresh the page when we make updates to our code.
  • CORS -- This CORS Express package will allow us to make requests from our frontend. Without it, we'll encounter the error message The CORS policy for this site does not allow access from the specified Origin.
  • Request -- We'll be making some external HTTP calls to the PetFinder API, so we'll use the Request package to do that. We could just use the built in HTTP module, but it's not very user-friendly.
  • body-parser -- This will allow us to extract the body (where the PetFinder data is) from our request so we can send it to the frontend

We can install these together with one command. For Nodemon, we're going to install it globally using the -g flag. This will allow us to call it just using nodemon instead of specifying the whole path. Again, make sure you're in the express-server directory and then run the following:


npm install nodemon -g
npm install express cors request body-parser --save

Create an API Endpoint

So just to get things up and running, I usually like to start simple and then come back and refactor (fancy word for clean up and move stuff around).

Let's make our file that will serve as our entry point. When we ran npm init, it designated the application's entry point to a file called index.js, so let's create that now.


touch index.js

This is our base file where we'll set the project up. First we need to import all those dependencies we installed and then we'll setup our first API endpoint for our client to read.


const express = require('express');
const app = express();
const bodyParser = require('body-parser');
const cors = require('cors');
const request = require('request');

app.use(bodyParser.json());
app.use(cors());
app.use(express.urlencoded({ extended: true }));

Here we're just telling our Node application to use all those dependencies we installed.

Next we need to start up the server. We just need to specify the port we want to listen on and then use express to listen.

// set the port
const port = 8000;

// listen on the port
app.listen(port);

Just one more thing before we actually run the command to startup the server. We need to go back to our package.json file and edit the script used to to start it up. In the scripts object, add a start command that will run nodemon index.js. Nodemon is the package we installed earlier that watches our code and automatically refreshes when anything is changed.

// package.json
"scripts": {
  "start": "nodemon index.js"
},

Once you've added that, just run the following command to start the server:


npm start

So our server is now running, but it's not actually doing much just yet. Let's setup a route the will do something when we hit the homepage. Go back to our index.js file and add the following code:


// index.js
app.get('/', (req, res) => {
    res.send(`Hi! Server is listening on port ${port}.`)
});

Now just navigate to localhost:8000 and you should see Hi! Server is listening on port 8000.!

So what exactly is happening here? First, we're creating an Express GET call that is executed when we navigate to the home route of our server (denoted by /), which is just localhost:8000.

Then in our callback function, we have access to the request and the response objects. We're going to send back a string specifying the port in our response.

Now before we get into the frontend code, let's just do ourselves a favor now and create some mock data in index.js to send over to the frontend. This will be essentially the same as above, except this time it will run when we hit localhost:8000/animals (you can test this out yourself) and we'll send back an array of objects. Each object will represent an animal available for adoption. Later on we'll come back and replace this with a real API call from PetFinder, but for now let's just keep it simple while we build out the base application. Onward to the frontend!


// index.js
app.get('/animals', (req, res) => {
  res.send([
    {
      id: 1,
      name: 'Lily',
      type: 'Dog',
      image: 'https://placedog.net/550/550',
      description: 'She loves to give kisses and bark at nothing.'
    },

    {
      id: 2,
      name: 'Lovely',
      type: 'Dog',
      image: 'https://placedog.net/500/500',
      description: `A little shy at first, but won't leave your side soon enough.` // note the backticks so we don't have to escape!
    },

    {
      id: 3,
      name: 'Sprinkles',
      type: 'Cat',
      image: 'https://placekitten.com/550/550',
      description: `Needs diabetes shot. Roll the insulin in your hand, don't shake it.`
    },

    {
      id: 4,
      name: 'Garbage',
      type: 'Cat',
      image: 'https://placekitten.com/500/500',
      description: 'A feral barn cat. He loves to eat garbage!'
    }
  ])
});

Create the Frontend

Now it's time to switch to our vue-client folder to setup the Vue.js frontend! First things first, we need to install it.

Install Vue.js

Since we already have npm installed, we can go ahead and use that to install Vue. This will install the Vue CLI, which will walk us through setting up our Vue app, similar to npm init. This time we'll use vue create .. The . just tells it to create the project in the current directory instead of making a new one. When it asks you if you want to install vue-router, select Yes. For everything else you can just select the default.

npm install -g @vue/cli
cd vue-client
vue create .
npm run serve

Another cool way to create a Vue app is using the Vue UI tool. It's a great way to see a visual representation of how the project is setup. It also gives you analytics, different configuration options, and more. You can read more about it here.

Now that our base project is created we can run npm run serve to boot it up! You should see a default Vue project.

Install Dependencies

Now that we have our Vue skeleton app, let's go ahead and add some dependencies now that we'll be using in our app.

Dependencies to install:

  • Buefy -- Bulma Components for quick styling
  • Axios -- To make our API calls to our server

Make sure you're still in the vue-client folder and then run:


npm install buefy axios --save

App Structure

Whenever I start a new project, I like to first map out the basic structure so that I'm not just stumbling around in the dark. It will most likely change as we begin to refactor, but we don't have to worry about that right now.

First inside the src/components folder, change the default HelloWorld.vue to AnimalList.vue. We'll also need a folder called src/layouts and 3 files: MainLayout.Vue to hold our base layout plus a Header.vue and Footer.vue.

cd src/components
mv HelloWorld.vue AnimalList.vue

cd ..
mkdir layouts

cd layouts

touch MainLayout.vue
touch Header.vue
touch Footer.vue

Setting Up

Let's jump right into our main.js file in our vue-client/src folder. This is going to be the entry point for our Vue app where we import the dependencies we installed and mount our app. We'll import Buefy (our Bulma styles) here so that we can use them throughout the entire project. Our Axios dependency will only be used in the file that we make the API calls from, so we don't need to add it here.

Your main.js file should now look like this:


// main.js
import Vue from 'vue';
import App from './App.vue';
import router from './router';
import Buefy from 'buefy';
import 'buefy/dist/buefy.css';

Vue.use(Buefy);
Vue.config.productionTip = false

new Vue({
  router,
  render: h => h(App),
}).$mount('#app')

You may have noticed we're importing another component here called App.vue. When we initialized the Vue app with the CLI, it created this entry point file for us. Let's go in now and edit it.

Open up App.vue and replace everything with the following:


// App.vue
<template>
  <MainLayout id="app">
    <router-view />
  </MainLayout>
</template>

<script>

import MainLayout from "@/layouts/MainLayout";

export default {
  components: { 
    MainLayout 
  }
};
</script>

Inside the main template, we're going to call the MainLayout component we created and give it the id app, which is what we mounted the Vue instance to in main.js earlier. Inside of that is <router-view />, which will control what shows inside the main layout based on the current route.

Now let's open up that MainLayout.vue file in vue-client/src and add:


// MainLayout.vue
<template>
  <div>
    <Header />
    <section class="section">
      <div class="container">
        <slot />
      </div>
    </section>
    <Footer />
  </div>
</template>

<script>
import Header from './Header';
import Footer from './Footer';

export default {
  components: { 
    Header, 
    Footer
  }
};

</script>

Our main layout will have the structure: header, custom component(s), and footer. That slot component is sort of a placeholder that will display whatever component the current route uses.

Header, Footer, and Homepage

We've created the header and footer components, now let's quickly fill them in courtesy of Bulma, which is an awesome CSS framework that can help us quickly build a beautiful website without having to focus too much on custom CSS. You can learn more about Bulma here.

We'll deal with the actual routes later, so just add "About" as a placeholder for now.


// src/layouts/Header.vue
<template>
  <nav class="navbar is-dark is-spaced" role="navigation">
    <div class="navbar-brand">
      <a class="navbar-item is-size-5 has-text-weight-bold" href="/">
        Furry Friends
      </a>
    </div>

    <div id="navbarMain" class="navbar-menu">
      <div class="navbar-start">
        <a class="navbar-item">
          About
        </a>
      </div>
    </div>
  </nav>
</template>

<script>
export default {
};

</script>

Now let's add a simple footer:


// src/layouts/Footer.vue
<template>
  <footer class="footer">
    <div class="content has-text-centered">
      <strong>Furry Friends</strong>
      <p>&copy; 2019</p>
    </div>
  </footer>
</template>

<script>
export default {
};

</script>

I chose a dark nav, but you can swap the colors out here pretty easily with Bulma's color classes.

Now we can move onto the Home component and work on filling in the middle portion! In the Home.vue let's just swap out the HelloWorld component for the new AnimalList component.


// Home.vue
<template>
  <AnimalList />
</template>

<script>

import AnimalList from '@/components/AnimalList.vue'

export default {
  components: {
    AnimalList
  }
}
</script>

We'll add more to the homepage later, but for now let's just focus on this AnimalList component. This is where all the magic happens! Open up src/components/AnimalList.vue and let's get started on connecting the two portions of our app together!

Connect the Backend and Frontend

So now that we have a frontend and a backend, both working separately, we need to figure out how to connect them. For the sake of clarity, we're first going to do this directly in the AnimalList component. Once it's clear what's going on, we'll come back and refactor.

Working with Axios

Replace everything in your AnimalList.vue file with this:

// AnimalList.vue

<template>
</template>

<script>

import axios from 'axios';

export default {
  name: 'AnimalList',
  data() {
    return { animals: [] }
  },
  methods: {
    async getAllAnimals() {
        const response = await axios.get('http://localhost:8000/animals');
        this.animals = response.data;     }

  },
  mounted() {
    this.getAllAnimals(); 
  }
}

</script>

There's a lot to unpackage here. First, we're importing axios, which is what we'll use to send out our HTTP request. Next in the data function, we're returning an object which initializes our animals array.

Next we have our methods object. In here we're create a function called getAllAnimals. Using axios, we'll send a GET request to hit the URL http://localhost:8000/animals. Remember back when we setup our server to listen on port 8000 and we created a route called /animals that would return some mock data to us? Well this is how we get it into our Vue app!

We'll save whatever response comes back into a variable called response. The response will contain information about our request such as headers, status code, and of course the data, which is what we're interested in.

To directly grab the data out, we'll set the animals object we created earlier to response.data. This will make it easier to work with when we start to loop through it for display. Remember to access the same animals variable from the data object, we have to add this before it.

Now the final thing we need to do is actually call the method we just created.

We want to call the function right away so that we can display the results as soon as possible. We can do this by using Vue's mounted function, which runs right as the component is mounted. Again, to access the function, make sure you add this before it.

Just a reminder to potentially save you some headache: make sure your server is still running for this next part! If it's not, enter into the express-server folder and run the command npm start.

Displaying the Data

We're finally in the home stretch! We have our backend server listening and our frontend wired up and making a call to the server to get our animal data. Now the last thing we need to do is display the results!

When working with a lot of data, especially external data that you didn't format yourself, it's sometimes helpful to first dump the results so we can see how we'll display it.


// AnimalList.vue

<template>
  <div>
    {{ animals }}
  </div>
</template>

Of course we did create this data ourselves and it's not very complex, but once we start pulling from PetFinder that little trick will come in handy.

So we have an animal array that contains 4 objects with properties name, image, type, description, and id.

We can loop through each animal using Vue's v-for directive. Whenever we use v-for, we also have to make sure we add v-bind:key to bind each item to its own unique ID.

We're going to split the layout up into 4 columns using Bulma's columns class along with is-one-quarter. To display the image, we have to bind the image url to the src attribute with v-bind:src. Next we just display the animal's name, description, and type of animal and we're good to go!


// AnimalList.vue
<template>
  <div>
    <div class="page-header">
      <h2 class="title">Animals available for adoption</h2>     </div>
    <div class="content columns is-multiline">
      <div v-for="animal in animals" v-bind:key="animal.id" class="column is-one-quarter">
        <div class="card">
          <div class="card-image">
            <img v-bind:src="animal.image">
          </div>
          <div class="card-content">
            <div class="media">
              <div class="media-content"> 
                <p class="title is-size-4">{{ animal.name }} <span class="is-size-6 has-text-grey"> {{ animal.type }}</span></p>
              </div>
            </div>
            <div class="content">
              {{ animal.description }}
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

<style scoped>
  .page-header {
    margin-bottom: 30px;
  }
</style>

Wrap up and What's Next

We've covered a lot in this tutorial! Let's back up a little and recap.

First we created our Express server. We setup an API endpoint with some mock data. Then we created a Vue frontend that hits that endpoint, receives the data, and displays it.

Now that we have the basic concept down, we can dive into getting real data from an external API.

In the next post, we'll learn how to get an access token from PetFinder and automatically refresh it when it expires. We'll use the token to pull live data about animals that are available for adoption and then format and display that data. Finally we'll go through and refactor our app to make it a bit cleaner.

Hope this was helpful and see you in the next one!

Nahoru
Tento web používá k poskytování služeb a analýze návštěvnosti soubory cookie. Používáním tohoto webu s tímto souhlasíte. Další informace