-
launch VS Code and open the
student
folder -
open the terminal and type the following commands, hitting
enter
after each one:cd app
npm install
-
launch Postman
-
create a new environment
-
hit the gear button in the upper right hand corner > Manage Environments > Add
-
Call it
Brewery API
-
Add the following (key, value) pairs:
key value host localhost port 5001 email auth <leave blank, we will fill this out later>
-
-
import the API tests from the
/student/API_Tests/Brewery_API.postman_collection.json
file- this will load all the API tests for the REST API (note: the tests will not work until the routes are created!)
Note: make sure you have Python's virtualenv
installed before doing this part!
-
Before we can launch our flask REST API (app), we need to make sure the dependencies required are available. For your convenience, I have provided a shell script to create a
virtualenv
and install the necessary modules for windows users. Find thesetup_venv.sh
file inside thestudent/Python
folder and double click to run.- If using a mac, skip this step as there is a
virtualenv
calledmac_venv
inside theapp
folder. (note: the script may not work for everyone, if you're having issues visit the Setting up the virtualenv manually page).
- If using a mac, skip this step as there is a
-
start the Flask service by double clicking the
run.sh
insdie thestudent/Python
folder- the terminal message should say it started the
virtualenv
and the app is running on port5001
- IMPORTANT - make sure to hit
Ctrl + C
to stop the app from within the terminal window, simply closing the window may not kill the python process!
- the terminal message should say it started the
-
Go back to Postman and find the
Brewery API
Collection in the left pane and expand it -
the first request in the list is a
GET
request calledendpoints
. Click on this.- notice the request url is set to
{{host}}:{{port}}/endpoints
. Postman will use our environment varialbes to fill in the host and port automatically. It is good practice to use variables for things like this to test development and production environments by simply swapping out the host and port variables. - Hit the
Send
button to make the requst. You should get a JSON response that looks like this:
- notice the request url is set to
{
"endpoints": [
{
"methods": "HEAD,OPTIONS,GET",
"url": "/"
},
{
"methods": "HEAD,OPTIONS,GET",
"url": "/downloads/[filename]"
},
{
"methods": "HEAD,POST,OPTIONS,GET",
"url": "/endpoints"
},
{
"methods": "HEAD,OPTIONS,GET",
"url": "/test"
},
{
"methods": "HEAD,OPTIONS,GET",
"url": "/tests/exceptions/[code]"
}
]
}
- the
/endpoints
request automatically shows all the available routes in our API. This will grow as we add more.
when working with HTTP
requests, there's a variety of ways data/query params can be passed back and forth. The app.utils
Python module has a powerful function called collect_args
, which will parse request arguments from the query string, form data, or raw json in the request body.
Next, run click on the /test
route in the Brewery API
Collection in Postman. In this request, there is a parameter in the query string as well as the body. This route just returns back a response of the arguments passed in (processed by collect_args()
) and the response should be:
{
"body_param": "bar",
"query_param": "foo"
}
Now that we know our REST API is working, it is time to start playing with our Brewery Data; but first, we need a database to work with. To create our database double click on the create_db_data.sh
file in the Python
folder. This will create a SQLite database called beer.db
inside the /Python/db
directory and load in some CSV files. You should see a success message pretty quick after the database is set up.
Before we start setting up the API methods, we need to should understand the data. The models.py
file contains the schema for our database tables represented as the following classes:
Brewery
- model forbreweries
tableBeer
- model forbeers
tableCategory
- model forcategories
table (beer categories) - we actually aren't using this table directlyStyle
- model forstyles
table (beer style)User
- model forusers
table (note this extends the flask_login.UserMixin class
We can view this data easier by looking at the actual beers.db
database in the DB Browser for SQLite application (can right click on the beers.db
and choose "Open With DB Browser for SQLite"). Choose the breweries
table in the Browse Data tab.
Note that the Brewery Information contains latitude and longitude values stored in the y
and x
fields respectively. This will be crucial for displaying the breweries in the map. Take a moment to look at the other tables as well to become familiar with the data and schema. Once complete, close the DB Browser for SQLite application to prevent any database locks.
- Go back into VS Code and check to make sure the
npm install
command completed successfully. The following dependencies should have installed (seepackage.json
):
vue
- Vue.js, our application frameworkvue-router
- Vue Router is a Vue Plugin to assist with creating Single Page Applications (SPAs)bootstrap-vue
- Bootstrap 4 bindings for Vue JS implemented as convenient Vue.js Componentsaxios
- client for making web requests@fortawesome/fontawesome-free
- fontawesome icon library (free version)@fortawesome/vue-fontawesome
- Vue.js wrapper for font awesome icons, essential for making reactive changes@fortawesome/fontawesome-svg-core
- needed forvue-fontawesome
for dom watching utilsmapbox-gl
- Mapbox GL JavaScript API that will provide our map interfacemapbox-gl-vue
- implements the Mapbox-gl Map as a Vue component for Vue.js
-
Now let's take a quick look at the code provided in the boiler plate in VS Code (
/student/app/src
): -
View the
main.js
file in VS Code. This begins with importing all the necessary modules. Some important things to note is that since we are using a plugin for Vue (bootstrap-vue
), we have to tell Vue to use it by doing:
Vue.use(BootstrapVue)
We are also going to register font awesome icons as a global component like this:
Vue.component('font-awesome-icon', FontAwesomeIcon);
Because this main.js
file is the entry point to the application and we do not want to hard code configuration in the application code itself such as the base url to our REST API and our mapbox access token, we are going to use a config.json
file which can be found in at app/public/config.json
. It should look like this:
{
"map": {
"accessToken": "pk.eyJ1IjoiZ2lzLWxpcyIsImEiOiJjam02Zmw2cDAzeGNjM3FsaTd0NmlzYTdvIn0.WCNnv0GbbVy624j8Dejs1A",
"mapStyle": "mapbox://styles/mapbox/streets-v10",
"center": [
-93.37,
44.9
],
"zoom": 9
},
"api_base": "http://localhost:5001"
}
If you have your own mapbox token, please use it. The token above is just a demo token and will not allow more than 50K map views per month. We are also setting a default map center as well as the mapbox tile style for the basemap (streets
in this case). The other important thing being set is the api_base
. By making this configuration driven, you can easily change from a development to production environment for the REST API being used in this application without having to change any of the app code itself. This is a best practice I would strongly recommend.
Now that we know about this config.json
file, we want to initialize our Vue instance within the application to store these configuration options. Because we have placed this config file in the public
folder, we can load it relatively to our application by simply making a request to ./config.json
. The reason we are loading this via axios
/xhr
is because if we were to place it next to the main.js
file and import config from './config'
, it would be bundled with all of our other application code when we build for production and it becomes a static asset within the code bundle instead of something that loads dynamically when the application initializes.
The rest of the main.js
file code is instantiating the Vue instance once the config is loaded and the promise is resolved. The config files is loaded using the request()
function from the modules/xhr.js
file, which simply provides a light wrapper for axios
. We will use the request
function quite heavily for our client side interactions with the REST API. Here is the code used for creating the Vue instance with some additional comments not found in the app code:
// wait for config file to load before initializing Vue instance
request('./config.json').then((config) => {
// set base url for our REST API from config file, this will be used globally
// axios to prepend the base url to all requests that start with /
axios.defaults.baseURL = config.api_base;
// our new Vue instance
new Vue({
// render function is called to bring in the template created in the App.vue file
render: h => h(App),
// register router with vue, this stores all the routes our single page app will use
router,
// mounted event, just logging to show when it is created
mounted(){
console.log('MOUNTED MAIN VUE INSTANCE');
},
// data for vue instance, it must be a function that returns an object
data(){
return {
// this is our configuration options
config: config
}
}
// finally 'mount' the Vue instance to the <div id=app></div> element in the App.vue file's template
}).$mount('#app');
});
Now that we know how the Vue instance is created, let's take a look at the App.vue
file. This file is also pretty slim, even though it represents the skeleton for the entire application. The <template>
tag only contains a few elements:
#app
div - container for entire vue instanceapp-nav-bar
- this is theAppNavBar
component from theAppNavBar.vue
file and will represent our app's navbarrouter-view
- this is the placeholder for all of our app screens or routes. It is wrapped in thekeep-alive
component to tell Vue to reuse the components rather than recreate them each time a route changes. In addition to the performance benefits, it will also maintain the state of each component when they are hidden and restored.
The other major thing stored in this file is the <style>
tag at the bottom. This is not scoped
by design as it will serve as our global css file. The main style classes we will use the most are the theme
and theme-banner
classes, which will use the two main colors I have chosen for this app: forestgreen
(primary) and orange
(secondary). If you do not like these colors, feel free to change these two classes as well as the assocated button.theme
and button.theme:hover
classes.
Note: the style
tag is not required for .vue
files and the css
or scss
can be stored as their own files and referenced via import
statements
The next impotant thing for the application is the Router
. This will control the navigation to the different pages in the application. The pages are:
- Home (
/
) - the home page that the user will see when they come to the app. This will contain the map view. - Signup (
/sign-up
) - this is the page users will see when they sign up/register with theBrewery Finder
app - Activation (
/users/:id/activate
) - when a user registers with the app, their account is created on the backend and a confirmation email is sent to allow the user to activate their account. The activation email will provide a link to this page. - PageNotFound (
*
) - this is our catch all route that will intercept any invalid routes that are matched (trying to visit a brewery that doesn't exist in the database, etc)
We probably won't get to cover all this, but the code is still provided in the solution for the routes below:
- EditableBrewery (
/brewery/:brewery_id
) - this will provide a place where authenticated users can create, update, or delete a brewery. Featured beers for the brewery can also be added from this screen. - EditableBeer (
/beers/:beer_id
) - This is where authenticated users can delete/update a featured beer for a brewery. It will also include a drag/drop zone where a photo of the beer can or bottle can be uploaded.
The only routes included in the routes.js
as part of the boiler plate are the Home
and PageNotFound
pages. When registering a route, at a minimum you must provide a path
for the route and the component
to render for that page. The path
represents what will be appended to the url, so for our home page, the url will be: http://<host>:<port?>/
for our development server, and for production it would be something like http://<host>.com/brewery-finder/
. A name
attribute can also be provided for programmatic navigation to different routes. The last thing to note is that the Router
's mode is set to history
, which will use the Browser's history API and you can navigate between routes by using the Browser's back button.
IMPORTANT - When using the Vue-Router
do not forget to tell Vue to use this plugin:
import Router from 'vue-router';
import Vue from 'vue';
// register with Vue
Vue.use(Router);
This will be the main page of the application and will contain the map view of the breweries. An important aspect that we will be building later is the Sidebar.vue
component, which will contain the Typeahead.vue
component that will allow users to search for breweries by name (wildcard match). Also in the Sidebar will be the BreweryInfo.vue
component that will be displayed for each brewery to give some basic info on the brewery and show their featured beers.
The MapViewMglv.vue
component is nested inside the Home.vue
component and uses the Mapbox-gl
JavaScript API under the hood. Identifying map breweries by clicking on the map will be handled here.
Accordion.vue
- Component representing a Bootstrap Accordion (collapsible menu)Spinner.vue
- Component with a spinner animation to indicate that resources are loadingDropZone.vue
- Component that will be used for uploading beer photos (bonus material)
Now that we are familiar with the current application structure, let's make sure the app works in it's current state. A dev server is shipped as part of the @vue-cli
to make the Development experience more pleasant by using some nice features such as Hot Reloading. To start the application through the dev server, run the following command in the VS Code terminal (or git bash, make sure you are in the app
directory!):
npm run serve
Once the dev build completes, the application should be running and should say at localhost:<port>
. You will see some es-lint
errors, but ignore these as they are just complaining about using console.log()
.
Note the @vue-cli
dev server is actually the webpack dev server under the hood. This supports many different configuration options.
Once the dev server has started, launch Chrome and navigate to the location shown in the terminal. The app should look similar to this:
Also, be sure to launch the Chrome Developer tools (F12
, Ctrl + Shift + I
, or right click > inspect). You should see the following console logs:
Pay close attention to the order of the console logs. The first thing loaded/mounted is the App.vue
and then anything that it contains is loaded after (Map View, followed by Home) with the Vue instance mounting last. The map loaded event will probably be the last log because that does the start up of Mapbox-gl
which also has to load in tiles. Right now the application doesn't really do anything other than display a map.
However, because we did implement the PageNotFound
route to catch any unmatched routes, you can have some fun by typing invalid route names into the url such as localhost:8080/error
to see this page displayed:
Now that the basic front end application is running and we have a working back end API as well, let's start building the fun stuff! Feel free to move on to Section 2 now.