Getting Started with Payload CMS & Vue JS
Tags: vue, headlesscms, ionic, webdev
Published: 2023-09-22 22:32:44


>From [Payload CMS](https://payloadcms.com/) Documentation - A headless CMS is a system that sticks to what it's good at—managing content. It concentrates solely on granting administrators an effective way to author and maintain content but doesn't control how and where that content is used.

Another definition I found

A headless CMS is a content management system that separates the presentation layer (where content is presented) from the backend (where content is managed). A headless CMS allows you to manage content in one place and be able to deploy that content on any digital channel you choose.

{% youtube TDd9EedIrGY %}

## Video Series
In this video series, we will set up [Payload CMS](https://payloadcms.com/) Headless CMS, create a Customers collection, log in as a Customer, create an account as a Customer. Then build a simple vuejs website that can login using the API created by [Payload CMS](https://payloadcms.com/) and finally look at the changes needed to go from website to mobile application with Ionic Framework.

> **[Series Playlist](https://www.youtube.com/playlist?list=PL2PY2-9rsgl3o_RwTgZ17E2_Z5SuseDbo)**

## Setup Payload CMS Server

- Payload CMS - [See Docs](https://payloadcms.com/)
- What is Payload CMS - [See Docs](https://payloadcms.com/docs/getting-started/what-is-payload)

Install payload with the following command in your terminal.

```tsx
npx create-payload-app
```

the database…

At this time Payload CMS only supports MongoDB.

>You can create a free account at Mongo DB Atlas for a cloud-hosted solution - [See Website](https://www.mongodb.com/atlas/database)

In this example, I will be using the docker file, `docker-compose.yml` provided, and only launch the Mongo database service.


### Project Configuration
After it is installed and you switch to the project directory you can configure the server. Open up the `.env` file and make the appropriate changes.

The installation process will add the basics, so all you will need to add is the `PAYLOAD_PUBLIC_SERVER_URL`, that value will be specific to your environment

```tsx
MONGODB_URI=mongodb://127.0.0.1:27017/payload-test-1
PAYLOAD_SECRET=4f5b1d73bb08944a9e6d2506
PAYLOAD_PUBLIC_SERVER_URL="http://localhost:3100"
```

Update the server config `payload.config.ts`

```tsx
// payload.config.ts
import { buildConfig } from "payload/config";
import path from "path";
import Users from "./collections/Users";
import { payloadCloud } from "@payloadcms/plugin-cloud";

export default buildConfig({
  serverURL: process.env.PAYLOAD_PUBLIC_SERVER_URL,
  cors: [ ],
  csrf: [ ],
  admin: {
    user: Users.slug,
  },
  collections: [Users],
  typescript: {
    outputFile: path.resolve(__dirname, "payload-types.ts"),
  },
  graphQL: {
    schemaOutputFile: path.resolve(__dirname, "generated-schema.graphql"),
  },
  plugins: [payloadCloud()],
});
```

you can open the `server.ts` file to make any additional configurations for the express server that is running Payload CMS. 

I will ensure the server port is the same as in my `.env` file. 

```tsx
// server.ts

import express from 'express';
import payload from 'payload';

require('dotenv').config();
const app = express();

// Redirect root to Admin panel
app.get('/', (_, res) => {
  res.redirect('/admin');
});

const start = async () => {
  // Initialize Payload
  await payload.init({
    secret: process.env.PAYLOAD_SECRET,
    mongoURL: process.env.MONGODB_URI,
    express: app,
    onInit: async () => {
      payload.logger.info("Payload Admin URL: " + payload.getAdminURL())
    },
  })

  // Add your own express routes here

  app.listen(3100);
}

start();
```

You can see I adjusted the port that the server is listening on to match what is in my `.env` file

```tsx
app.listen(3100);
```
### Creating the Customer Collection
Finally, let's create a new collection called `Customers`, this will be the collection of application users. Payload CMS creates a default collection `Users`, which in this example will be system administrators and people who need to login directly to the Payload CMS console

The code for the Customer Collection is listed below.

```tsx
// /collections/Customers.ts

import { CollectionConfig } from "payload/types";

const Customers: CollectionConfig = {
  slug: "customers",
  auth: true,
  admin: {
    useAsTitle: "email",
  },
  fields: [
    // Email added by default
    // Add more fields as needed
    {
      name: "full_name",
      type: "text",
      label: "Full Name",
      required: true,
    },
    {
      name: "birthday",
      type: "date",
      label: "Birthday",
      required: true,
    },
  ],
};

export default Customers;
```

Update configuration in `payload.config.ts` to include the new Collection `Customers`

```tsx
// payload.config.ts

import { buildConfig } from 'payload/config';
import path from 'path';
import Users from './collections/Users';
import { payloadCloud } from '@payloadcms/plugin-cloud';
import Customers from './collections/Customers';

export default buildConfig({
  serverURL: process.env.PAYLOAD_PUBLIC_SERVER_URL,
  admin: {
    user: Users.slug,
  },
  collections: [
    Users,
    Customers
  ],
  typescript: {
    outputFile: path.resolve(__dirname, 'payload-types.ts'),
  },
  graphQL: {
    schemaOutputFile: path.resolve(__dirname, 'generated-schema.graphql'),
  },
  plugins: [
    payloadCloud()
  ]
});
```

Login to Console
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mfo8bhj9wtk3bgjrp2zz.png)

View Collections
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/t22z0xkzygplcugbtnvi.png)

Create A Customer
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3cxphftoup4mqpqezymc.png)

After manually entering a customer you get this view.
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/d5wkuwwlm0wwj6jbbvc3.png)


### REST API or GraphQL API, Take What You Like
Ad you can see the API URL in the lower right that you can click on and see the JSON object returned. This URL is using REST API by default.



I will be using REST API in this example, additional information in using the REST API can be found here - [REST API Overview](https://payloadcms.com/docs/rest-api/overview) 

```tsx
http://localhost:3100/api/customers/650dd606d137671cd4d62593?locale=en
```

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/utr7eehfhs0dvh8heg7y.png)

You also get GraphQL support out of the box, see the playground here http://localhost:3100/api/graphql-playground. Be sure to use the URL that is appropriate for your configuration.

>Additional information on GraphQL API - https://payloadcms.com/docs/graphql/overview

I can make the same query as I did above

```graphql
query getCustomer {
  Customer(id: "650dd606d137671cd4d62593") {
    id
    full_name
    email
    birthday
    createdAt
    updatedAt
  }
}
```

And get the correct results

```json
{
  "data": {
    "Customer": {
      "id": "650dd606d137671cd4d62593",
      "full_name": "aaron clerarlyinnovative",
      "email": "aaron@mail.com",
      "birthday": "2023-09-22T04:00:00.000Z",
      "createdAt": "2023-09-22T17:59:34.571Z",
      "updatedAt": "2023-09-22T18:01:10.593Z"
    }
  }
}
```

## Setup Ionic Vue Client

> [Ionic Framework](https://ionicframework.com/) UI Components are used to build a website and then a mobile application is built using [Ionic Capacitor](https://capacitorjs.com/). Ionic UI components are not required but are used for UX. The vue js code presented here will work fine in a separate application.

Use Ionic Framework cli to create a new Vue js project using the blank template.

```tsx
npx ionic start --type vue
```

Update the router in the new project to support the two new routes, `/sign-in` and `/sign-up`. Make the following changes to `/router/index.ts`

```tsx
import { createRouter, createWebHistory } from '@ionic/vue-router';
import { RouteRecordRaw } from 'vue-router';
import HomePage from '../views/HomePage.vue'
import SignIn from '@/views/SignIn.vue';
import SignUp from '@/views/SignUp.vue';

const routes: Array<RouteRecordRaw> = [
  {
    path: '/',
    redirect: '/home'
  },
  {
    path: '/home',
    name: 'Home',
    component: HomePage
  },
  {
    path: '/sign-in',
    name: 'SignIn',
    component: SignIn
  },
  ,
  {
    path: '/sign-up',
    name: 'SignUP',
    component: SignUp
  }
]

const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes
})

export default router
```

### Create Login Page

Create a new file in the `views` directory called `SignIn.vue` and add the code below to create the basic page template.

```html
<template>
  <ion-page>
    <ion-header :translucent="true">
      <ion-toolbar>
        <ion-title> Payload CMS - Ionic Vue</ion-title>
      </ion-toolbar>
    </ion-header>

    <ion-content :fullscreen="true" >
      <h3 style="margin-left:16px">Sign In</h3>
      <ion-list :inset="true">
        <form @submit.prevent="handleLogin">
          <ion-item>
            <ion-input
              v-model="email"
              name="email"
              label="Email"
              label-placement="stacked"
              autocomplete="email"
              type="email"
            ></ion-input>
          </ion-item>
          <ion-item>
            <ion-input
              v-model="password"
              name="password"
              label="Password"
              label-placement="stacked"
              autocomplete="new-password"
              type="password"
            ></ion-input>
          </ion-item>
          <div class="ion-text-center">
            <ion-button type="submit" fill="clear">Sign In</ion-button>
            <ion-button
              type="button"
              fill="clear"
              @click="router.push('/sign-up')"
              >Create A New Account</ion-button
            >
          </div>
        </form>
      </ion-list>
    </ion-content>
  </ion-page>
</template>
```

Add Imports and component properties to the file.

```html
<script setup lang="ts">
import {
  IonContent,
  IonHeader,
  IonPage,
  IonTitle,
  IonToolbar,
  IonList,
  IonItem,
  IonInput,
  IonButton,
  useIonRouter,
} from "@ionic/vue";
import { ref } from "vue";

... new code goes here

</script>
```

```tsx

const email = ref("");
const password = ref("");
const router = useIonRouter();
```

### Add Login Function
The login function is going to make a REST API call to the Payload CMS backend to authenticate the user.

We use the refs we created above to pass the `email` and `password` values to the API call and if successful it will return the user information and the JWT for for authenticating the user in future API calls
```tsx
const handleLogin = async () => {
  try {
    const resp = await fetch("http://localhost:3100/api/customers/login", {
      method: "POST",
      credentials: "include",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        email: email.value,
        password: password.value,
      }),
    });

    if (!resp.ok) {
      const errorMsg = (await resp.json())?.errors[0].message;
      throw new Error(errorMsg);
    }
    const user = await resp.json();
    console.log(user);

    // goto home
    router.replace("/home");
  } catch (error: any) {
    alert("Sign In Error " + error.message);
  }
};
```

Update server to address CORS issue; add the two lines below to the server configuration in `payload.config.ts`

```typescript
  cors: ['http://localhost:5173'],
  csrf: ['http://localhost:5173'],
```

Enter the path to sign in page into your browser, and then the credentials for the Customer you created in the CMS

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bkd3wo3f3uhrbai3j1z6.png)

Confirm login worked by looking at the web console

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6nkshihbobsv4q2plp7k.png)

We can also look in the web developer console and see that we have an HTTP cookie from the server that contains the `JWT` for the authenticated user

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/71z74pchu29vwbtvg8w0.png)

### Full Source Code
{% github aaronksaunders/payload-vue-ionic-video%}