Building an Online Menu using Apostrophe Headless + Nuxt / Nginx: Part 1
Tags: docker, webdev, javascript, tutorial
Published: 2020-11-20 14:22:49

In this tutorial, we'll demonstrate how to use [Apostrophe Headless] (https://github.com/apostrophecms/apostrophe-headless) with [Nuxt] (https://nuxtjs.org/) for the frontend and [Nginx] (https://www.nginx.com/) as a reverse-proxy, as well as optional [Docker] (https://www.docker.com/) instructions for Docker users. We'll be creating an online storefront for a restaurant that will allow customers to register and place an order. Let's dive in.

### Prerequisites
If not using Docker, the following packages and applications are prerequisites for this tutorial, which has been written with **macOS** users in mind. If you're starting fresh, we strongly recommend using [Homebrew] (https://brew.sh/).

- Git: `brew install git`
- Node / npm: `brew install node`
- MongoDB: `brew tap mongodb/brew && brew install mongodb-community && brew services start mongodb-community`
- ImageMagick: `brew install imagemagick`

##Getting Started
Start by cloning the [project] (https://github.com/falkodev/apos-nuxt-demo) into your preferred directory using the terminal.

`git clone git@github.com:falkodev/apos-nuxt-demo.git && cd apos-nuxt-demo`

Next, we'll walk through the process of running the application, with and without Docker.

####Without Docker
1. In your terminal, run `npm run install-app` to install the application.
2. Next, run `cd backend && node app fixtures:all && cd ..` install the project fixtures.
3. When complete, run `npm run dev` to start the developer environment.

The application will run at http://localhost:3333 and http://localhost:1337/cms for the front-end and back-end respectively. If you run into any issues, be sure to check that you have the prerequisites covered above installed correctly.

💡If you prefer having separate logs for the front-end and back-end, you can do so by running the following commands for front-end and back-end respectively in separate terminal windows.

`cd frontend && npm run dev`

`cd backend && npm run dev`

####With Docker
1. In your terminal, run `make`.
2. Run `docker-compose exec demo-backend node app fixtures:all` to install the project fixtures.
3. When launched, the front-end will run at http://localhost and the back-end will run at http://localhost/cms.

If you prefer having separate logs for the front-end and back-end, run `make logs-back` and `make logs-front` in separate terminal windows.

####Docker-Compose

The `docker-compose.yml` file describes the various containers and how they are organized:

1. `demo-db` container for the MongoDB image.
2. `demo-backend` container for Apostrophe, using MongoDB.
3. `demo-frontend` container for Nuxt, contacting the backend on http://demo-backend:1337/cms.
4. `demo-reverse-proxy` container for Nginx.

####Reverse-Proxy

The `reverse-proxy/local.conf` declares how requests are dispatched:

```
​listen 80;
server_name localhost;
root /usr/share/nginx/frontend;

location / {
  proxy_pass http://demo-frontend:3333;
}

location /cms/ {
  proxy_pass http://demo-backend:1337;
}
```
Everything on port `localhost:80` will be redirected to Nuxt, except for urls pointing to `/cms`, which are redirected to Apostrophe.

In its configuration, Apostrophe has a matching prefix:

```
// in backend/app.js
prefix: '/cms',
```
This way, with Docker, you can access the frontend on http://localhost and the backend on http://localhost/cms.

##Setting up Shop
Now that we're successfully running the application with the fixtures installed, let's take a look at the rendered project in the browser.

💡You can navigate to `backend/lib/modules/fixtures/index.js` to explore how Apostrophe fetches and inserts documents into MongoDB.

####Back-End
1. If you're using Docker, navigate to http://localhost/cms/login in your browser. Otherwise, navigate to http://localhost:1337/cms/login.
2. The credentials for both user and password are `admin`.

![admin bar](https://dev-to-uploads.s3.amazonaws.com/i/b4pj745a4hg8881dr8vd.png)

You can click on "Menu Items" to look at the generated products by the "fixtures" step.

![menu items](https://dev-to-uploads.s3.amazonaws.com/i/pcsi3m6q44kt9p1jzepw.png)

####Front-End
If you're using Docker, navigate to http://localhost in your browser. Otherwise, navigate to http://localhost:3333. If you follow the steps above, you should see an index of Menu Items.

![menu dishes](https://dev-to-uploads.s3.amazonaws.com/i/rcktvof1e533mu6sh4is.png)

##Looks delicious, but how does this work?

####The Menu Item Schema

On the back-end, [apostrophe-headless] (https://github.com/apostrophecms/apostrophe-headless) has been installed, and the menu-item module has been declared as a headless module with the `restApi` option set to `true`, automatically exposing a REST route at `/api/v1/menu-items`. 

```
// backend/lib/modules/menu-items/index.js
module.exports = {
  extend: 'apostrophe-pieces',
  name: 'menu-item',
  alias: 'menuItem',
  restApi: true, // /api/v1/menu-items
  ...
}
```

####Fetching the Menu
On the front-end, the index page fetches the menu items on this route, and Nuxt declares pages in the pages folder. In the asyncData method in this component, the following GET request is made:

```
​// frontend/pages/index.vue
const { results } = await $axios.$get('/api/v1/menu-items')
```

####Component Template
The menu items are displayed in the component's `template` section with a standard `v-for` loop. As you can see, Apostrophe is automatically processing the images in the `one-third` format.

```
​// frontend/pages/index.vue
<template>
  <section class="home">
    <div class="home-menu-items">
      <div v-for="item in menuItems" :key="item._id" class="home-menu-items__item">
        <img :src="item.image._urls['one-third']" />
        <h3>{{ item.title }}</h3>
        <span>{{ item.description }}</span>
      </div>
    </div>
  </section>
</template>
```

##Customizing the Home Page
While everything above is a great starting point, we'll want customize to the home page to facilitate our brand messages or provide customers with additional information. You could edit the Vue component to add some additional static text, but this is a CMS, so let's use it to add an area of CMS managed content to keep content editors happy. For that task, the fixtures have already configured the page type "home" in the [apostrophe-pages] (https://docs.apostrophecms.org/reference/modules/apostrophe-pages/) module:

```
​// backend/lib/modules/apostrophe-pages/index.js
module.exports = {
  restApi: true,
  apiTemplates: ['front-home'],
  types: [
    {
      name: 'front-home',
      label: 'Front Home',
    },
  ],
  ...
}
```

The `restApi` option is set to true, and we define a single page type for an Apostrophe editor to create: `front-home`. With [apiTemplates: ['front-home']] (https://github.com/apostrophecms/apostrophe-headless#rendering-a-page-or-piece-as-an-html-fragment) we indicate to expose the rendered template.

Let's create a home page by navigating to the admin in our browser and selecting "Pages" in the Apostrophe admin bar. From there, we can select "New Page" and begin filling out the requisite fields.

![new page](https://dev-to-uploads.s3.amazonaws.com/i/df0lncgpywbwfef6hbf2.png)

For our purposes, we're only focused on the `type` field: "Front Home". Choose "Save", and Apostrophe will redirect you to the newly created page. You can close the admin bar by clicking the Apostrophe logo on the far-left.

##Adding Content
Now that we've added a home page, we're ready to start customizing. Let's start by adding *Rich Text*, to give our Menu a quick introduction. When hovering over the page, you should now see a small "+" button. Clicking this will display the available [Widgets] (https://docs.apostrophecms.org/reference/glossary.html#widgets) for the page. Select "Rich Text". 

![widgets](https://dev-to-uploads.s3.amazonaws.com/i/f585nymtxfdhe8793jo2.png)

You'll be presented with a rich text editor. Type anything you want, for example "Fresh food and delicious ingredients" and click out of the area. Now you can go to the front-end and reload the page (http://localhost on Docker, http://localhost:3333 otherwise) to see the changes.

![menu ready](https://dev-to-uploads.s3.amazonaws.com/i/4dz40fb46womjr5ho449.png)

####How does this work?
In our `index.vue` component in `frontend/pages/`, the `asyncData` method fetches the pages exposed by Apostrophe and finds the home page we created.

##Adding a Login
On the back-end, hovering just below the rich text area, a green bar will appear and you can add another widget. This time, choose "Link". Below are suggested settings (if you are not using Docker, be sure to type http://localhost:3333/login in the `url` field): 

![login add](https://dev-to-uploads.s3.amazonaws.com/i/ymq9nycmzzrh48yan7ny.png)

The button is visible on the front-end after a refresh.

![login 2](https://dev-to-uploads.s3.amazonaws.com/i/d2zh4nx0v7mwms5toq13.png)

You can click the "Login" button and it will lead you to the login page.

 
##Next Steps
In [Part 2] (https://apostrophecms.com/blog/apostrophe-headless-nuxt-nginx-part-2) of this tutorial, we'll create a customer and order some food! We'll be releasing this next week, but if you can't wait to dig in, see the complete [documentation on Github] (https://github.com/falkodev/apos-nuxt-demo). Looking for additional guidance or want to show off your application of [Apostrophe Headless] (https://github.com/apostrophecms/apostrophe-headless)? Join the [discussion in Discord] (http://chat.apostrophecms.org/) and read our full [documentation] (https://docs.apostrophecms.org/).

We are continually inspired by the work of our community, and we can't wait to see what you build next. 


This post was written by Apostrophe’s resident philosopher, Senior Software Engineer, and all-around French family man. We also call him [Anthony] (https://github.com/falkodev).