Build a CV Generator Using Cloudinary and Xata in Vue
Tags: vue, cloudinary, xata, javascript
Published: 2022-11-24 11:16:25

A Curriculum Vitae, often referred to as a CV, summarizes someone's career, qualifications, and education. A well-crafted CV highlighting one's achievements or proficiencies is an art. 
For junior or entry-level professionals looking for their first jobs, sometimes it will take to get the next role to have a stellar CV that adequately speaks of one's abilities without the reader getting bored.

This article will discuss building a CV generator in Vue using Cloudinary and Xata.

[Cloudinary](https://cloudinary.com/signup?utm_source=hackmamba&utm_campaign=hackmamba-hackathon&utm_medium=hackmamba-blog) is a cloud-based video and image management platform that offers services for managing and transforming uploaded assets for usage on the web. The OCR add-on for text detection and extraction is also a part of the solutions rendered by Cloudinary.

Xata is a [Serverless Data platform](https://xata.io/docs/intro/serverless-data-platform?utm_source=hackmamba&utm_campaign=hackmamba-hackathon&utm_medium=hackmamba-blog) that simplifies how developers work with data by providing the power of a traditional database with the usability of a SaaS spreadsheet app.


## GitHub

Check out the complete source code [here](https://github.com/MoeRayo/cv-generator).


## Netlify

The link to the live demo can be found [here](https://polite-crepe-ad6881.netlify.app/).

## Prerequisite

Understanding this article requires the following:

- Installation of Node.js
- Basic knowledge of JavaScript
- A Cloudinary account (sign up [here](https://cloudinary.com/signup?utm_source=hackmamba&utm_campaign=hackmamba-hackathon&utm_medium=hackmamba-blog))
- Creating a free account with [Xata](https://xata.io?utm_source=hackmamba&utm_campaign=hackmamba-hackathon&utm_medium=hackmamba-blog)

## Creating a Vue app

We will create a new Vue app using the `vue create <project-name>` command.
The process of scaffolding the project would provide a list of options, which could look like this:

![Vue application scaffolding options](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v0d9i4gtwfrgd6o2g1mn.png)

We can start our Vue application by running the following command:

```javascript
cd <project name>
npm run serve
```
Vue will, in turn, start a hot-reloading development environment that is accessible by default at `http://localhost:8080`.

## Creating a Xata Database

To create a database on Xata, we will need to either log into our accoount or create a new [account](https://xata.io?utm_source=hackmamba&utm_campaign=hackmamba-hackathon&utm_medium=hackmamba-blog). Next, we will go to the database tab on the user's dashboard to create an appropriate database.


![Database creation on Xata](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ibzxp9nyevkal0no39d6.png)

We will then create a new table and add the appropriate records/fields that we would want the table to have. In this case, we created a `user` table with the `email`, `password`, and `username` records.
These records signify the data we will be tracking in our application and saving to the database.


![Table creation on Xata](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/z8eh7vp2xiay2lip6nfo.png)

## Setting up Xata in our Vue application

### Installing Xata
To use Xata in our project, we will install the Xata software development kit (SDK) from the command line like so:

```javascript
npx xata
```
After this, we can then initialize Xata for use in our application by running the command below:

```javascript
xata init
```
Xata will present us varieties of options from which we can choose. In the end, Xata will generate some files for usage, among which we will have the `.xatrc` and `.env` files.
We will need to edit the name of the Xata api key in our Vue app to the one below to allow the Vue application to pick up the environment variable.

```javascript
VUE_APP_XATA_API_KEY="add your api key"
```

## Cloudinary Setup

For our application’s asset management, if you haven’t already, you need to create an account with Cloudinary by clicking [**here**](https://cloudinary.com?utm_source=hackmamba&utm_campaign=hackmamba-hackathon&utm_medium=hackmamba-blog). Creating an account is entirely free.
We will need to install the [Cloudinary Vue.js SDK](https://cloudinary.com/documentation/vue_integration?utm_source=hackmamba&utm_campaign=hackmamba-hackathon&utm_medium=hackmamba-blog) with the command below before configuring it in your Vue app.


```javascript
npm install cloudinary-vue
```
We will need to configure the installed SDK in our Vue app by navigating to the `main.js` and adding the following to the file.

```javascript
import Cloudinary from "cloudinary-vue";

Vue.use(Cloudinary, {
  configuration: { 
    cloudName: "XXX" //add the cloudinary cloudname here,
    secure: true }
});
```
We will upload the template we want to use to our cloudinary application. In this case, we used this CV [template](https://docs.google.com/document/d/1nFSNBuXxRnyjRtWD-G9QxgUOdGu4TtRc0W54Ll7lgdk/edit?usp=sharing) that helped [Danny Thompson](https://twitter.com/DThompsonDev) land his first tech role.

## Creating the Signup Page

We will need a user to sign up for our application to generate a CV. To do this, we will create a file named, `views/SignupView.vue` and add the following code to it:

```javascript

<template>
  <div class="bg-gold vh-100">
    <div class="pv5 ph2">
      <form class="ba b--black bw4 bg-white br2 mw6 w-40-m w-70 w-20-l center pa3 shadow-5" @submit.prevent="signUp">
        <h2 class="ttc tc">
          Sign up
        </h2>
        <label for="name" class="db mb2 black-70">Name</label>
        <input
          id="name"
          v-model="username"
          name="name"
          type="text"
          class="db mb3 w-100 br2 ph2 pv3 ba bw1 b--black"
          placeholder="John Doe"
        >
        <label for="email" class="db mb2 black-70">Email</label>
        <input
          id="email"
          v-model="email"
          name="email"
          type="email"
          class="db mb3 w-100 br2 ph2 pv3 ba bw1 b--black"
          placeholder="example@email.com"
        >
        <label for="password" class="db mb2 black-70">Password</label>
        <input
          id="password"
          v-model="password"
          name="password"
          type="password"
          class="db mb3 w-100 br2 ph2 pv3 ba bw1 b--black"
          placeholder="••••••••"
        >
        <button type="submit" class="center db pa3 mb3 tracked bg-black ba br3 white pointer hover-black hover-bg-gold bg-animate pointer">
          Sign up
        </button>
        <p>Already have an account? <a href="/signin" class="black-70 b">Sign in</a> </p>
      </form>
    </div>
  </div>
</template>
```
By this, we have successfully created an interface for our Signup page. Now we will add the functionality for authentication and authorization. 

We will use Xata to accomplish our authentication goal. To do this, we will add this to the `SingupView.view` file:

```javascript
<script>
import { getXataClient } from '@/xata'
export default {
  name: 'signup',
  data: () => ({
    username: '',
    email: '',
    password: '',
  }),
  methods: {
    async signUp() {
      const xata = getXataClient()
      const user = await xata.db.users.filter('username', this.username).getFirst()
      if (!user) {
        await xata.db.users.create({
          username: this.username,
          password: this.password,
          email: this.email
        }).then((res) => {
          this.$router.push({path:`/dashboard/${res.username}`, params: user})
        })
        this.$notify({type: 'success', text: "Account creation successful!" })
      }
    }
  }
}
</script>
```
From the code block above, we achieved the following:

- Imported the Xata client 
- Checked if a user exists and created a new user if the current user does not exist
- At successful account creation, the user’s view is changed to the dashboard from where they can generate a CV.

At this point, our application should look like the below:


![Signup interface](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tyqb9eusvldqv8ae8b7o.gif)

## Creating the SignIn page

We also need to create options to allow signing in for people who have already created an account. To do this, we will create a file called `SigninView.vue` and add the code below:

```javascript

<template>
  <div class="bg-gold vh-100">
    <div class=" pv5 ph2">
      <form class="ba b--black bw4 bg-white br2 mw6 w-40-m w-70 w-20-l center pa3 shadow-5" @submit.prevent="signIn">
        <h2 class="ttc tc">
          Sign In
        </h2>
        <label for="email" class="db mb2 black-70">Email</label>
        <input
          id="email"
          v-model="email"
          name="email"
          type="email"
          class="db mb3 w-100 br2 ph2 pv3 ba bw1 b--black"
          placeholder="example@email.com"
        >
        <label for="password" class="db mb2 black-70">Password</label>
        <input
          id="password"
          v-model="password"
          name="password"
          type="password"
          class="db mb3 w-100 br2 ph2 pv3 ba bw1 b--black"
          placeholder="••••••••"
        >
        <button type="submit" class="center db pa3 mb3 tracked bg-black ba br3 white pointer hover-black hover-bg-gold bg-animate pointer">
          Sign in
        </button>
      </form>
    </div>
  </div>
</template>
```
The above creates a Signin interface for us. But we will need to wrap some functionalities around it to make it work as expected. 
To do this, we will add the code below in the `SigninView.vue`. 

```javascript

<script>
import { getXataClient } from '@/xata'
export default {
  data: () => ({
    email: '',
    password: ''
  }),
  methods: {
    async signIn() {
      const xata = getXataClient()
      const user = await xata.db.users.filter('email', this.email).getFirst()
      if (!this.email || !this.password ){
        this.$notify({type: 'error', text: "Please fill all empty fields"})
      } else if (this.email !== user.email || this.password !== user.password){
        this.$notify({type: 'error', text: "Incorrect credentials"})
        this.email = '';
        this.password = '';
      } else {
        this.$notify({type: 'success', text: "Login successful!"})
        this.$router.push({path:`/dashboard/${user.username}`, params: user})
      }
    }
  }
  
}
</script>
```
From the above, we created a function called `signIn` function that does the following:

- Imported the Xata client 
- Checks if the user passes an `email` and `password`, then returns the appropriate error if it is unavailable
- Cross checks the email and password entered to be sure it matches the email and password from the database
- If the checks pass, the page is routed to the dashboard, where the user can generate a CV

At this point, our interface will look like this:


![Signin interface](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/o7ovzso57evzy0jdsrhr.gif)

## Creating the dashboard

Our dashboard is where the bulk of the work happens, as this is where we will generate the CV. We will start by creating a file called `TheDashboardBody.vue`, and we will then add a form that allows the user to add the inputs they want to be shown on their CV.

```javascript

<template>
  <div class="flex absolute vh-auto top-4 left-2 right-2 w-90 center mw9">
  <div class="mr3 bg-white w-70 br3 pa3">
    <form @submit.prevent="createResume" class="flex justify-between flex-wrap">
      <div class="w-50-l w-100">
        <label for="email" class="db mb3 black-70 ttu fw7">Firstname</label>
        <input
          id="firstname"
          v-model="userData.firstname"
          name="firstname"
          type="text"
          class="db mb3 w-90 br2 pa2 ba bw1 b--black bg-black-05"
          placeholder="Firstname"
        >
      </div>
      <div class="w-50-l w-100">
        <label for="lastname" class="db mb3 black-70 ttu fw7">Lastname</label>
        <input
          id="lastname"
          v-model="userData.lastname"
          name="lastname"
          type="text"
          class="db mb3 w-90 br2 pa2 ba bw1 b--black bg-black-05"
          placeholder="Lastname"
        >
          </div>
          <div class="dn" v-if="showTemplate">
          <TheResumeTemplate :userData="userData" ref="ref" />
        </div>
      
        <div class="w-100">
          <button type="submit" class="f6 ttu tracked black-80 bg-black pa3 br3 white bb link b--black hover-black hover-bg-gold bg-animate pointer">Create Resume</button>
        </div>
        </form>
      </div>
      <div class="bg-white w-30 br3 pa3">
      <iframe :src="iframeSRC ? iframeSRC : initialSRC" width="100%" height="100%" />
    </div>
  </div>
</template>
```
Above is the template of how the dashboard interface will look.
We added the input fields for the data we will add to the CV from the above. We also added an iframe showing the generated CV in PDF form.

Now we will add the functionality to effect the changes we would like to see in the generated PDF.

```javascript

<script>
import TheResumeTemplate from "@/components/TheResumeTemplate.vue";
export default {
  components: {
    TheResumeTemplate,
  },
  data: () => ({
    userData: {
      firstname: '',
      lastname: '',
      },
    showTemplate: false,
    iframeSRC: '',
    initialSRC: 'https://res.cloudinary.com/xxx/image/upload/v1667335911/cv_gen.pdf'
  }),
  methods: {
    createResume(){
      if(!this.userData.firstname){
        this.showTemplate = false
      } else {
        this.showTemplate = true
        this.$nextTick(() => {
          this.iframeSRC = this.$refs.ref.$refs.ref.$el.src;
        });
      }
    }
  }
  
}
</script>
```
From the above, we could connect our `createResume` method to the template, allowing us to generate the PDF.
The complete code for this interface and functionality for this dashboard can be found in this GitHub [gist](https://gist.github.com/MoeRayo/633129e6698f1ed5990e4ab300d7e107).

## Adding the Cloudinary CV template

We will employ Cloudinary’s powerful features to generate and overlay content to finish our CV generation. To do this, we will create a new file called `TheResumeTemplate.vue` and add the code below.

```javascript

<template>
  <div>
    <cld-image publicId="cv_gen.pdf" ref="ref" >
    <cld-transformation flags="rasterize" />
      <cld-transformation :overlay="{fontFamily: 'Open Sans', fontSize: 75, text: `${userData.firstname} ${userData.lastname}`}" color="#58697B" gravity="west" x="100" y="-670"/>
</cld-image>
      
  </div>
</template>
<script>
export default {
  name: "TheResumeTemplate",
  props: {
    userData: { 
      type: Object, 
      required: true 
    }
  },
}
</script>
```
From the above, we added a `props` which will have its value filled from the `TheDashboardBody.vue`. We also used Cloudinary’s transformation features to overlay texts as we would prefer.
The complete implementation of the Cloudinary CV template can be found in the GitHub gist [here](https://gist.github.com/MoeRayo/929d855b9cd549328d1a4adfb28fb7e1).

At this point, our application will look like the below:

![Final application](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ubjungxktx88x87zz69z.gif)

## Conclusion

This article discusses generating a CV using Cloudinary and Xata. Cloudinary was used for image transformation, while we used Xata for authentication.

## Resources
- [Querying data from a Next.js app](https://xata.io/docs/quickstart/building-a-web-app-with-xata-and-nextjs#adding-xata-to-our-application?utm_source=hackmamba&utm_campaign=hackmamba-hackathon&utm_medium=hackmamba-blog)
- [Cloudinary transformation reference](https://cloudinary.com/documentation/transformation_reference?utm_source=hackmamba&utm_campaign=hackmamba-hackathon&utm_medium=hackmamba-blog)