DEV Community

Cover image for Build a CV Generator Using Cloudinary and Xata in Vue
Moronfolu Olufunke for Hackmamba

Posted on

Build a CV Generator Using Cloudinary and Xata in Vue

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 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 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.

Netlify

The link to the live demo can be found here.

Prerequisite

Understanding this article requires the following:

  • Installation of Node.js
  • Basic knowledge of JavaScript
  • A Cloudinary account (sign up here)
  • Creating a free account with Xata

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

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

cd <project name>
npm run serve
Enter fullscreen mode Exit fullscreen mode

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. Next, we will go to the database tab on the user's dashboard to create an appropriate database.

Database creation on Xata

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

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:

npx xata
Enter fullscreen mode Exit fullscreen mode

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

xata init
Enter fullscreen mode Exit fullscreen mode

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.

VUE_APP_XATA_API_KEY="add your api key"
Enter fullscreen mode Exit fullscreen mode

Cloudinary Setup

For our application’s asset management, if you haven’t already, you need to create an account with Cloudinary by clicking here. Creating an account is entirely free.
We will need to install the Cloudinary Vue.js SDK with the command below before configuring it in your Vue app.

npm install cloudinary-vue
Enter fullscreen mode Exit fullscreen mode

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.

import Cloudinary from "cloudinary-vue";

Vue.use(Cloudinary, {
  configuration: { 
    cloudName: "XXX" //add the cloudinary cloudname here,
    secure: true }
});
Enter fullscreen mode Exit fullscreen mode

We will upload the template we want to use to our cloudinary application. In this case, we used this CV template that helped Danny Thompson 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:


<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>
Enter fullscreen mode Exit fullscreen mode

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:

<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>
Enter fullscreen mode Exit fullscreen mode

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

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:


<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>
Enter fullscreen mode Exit fullscreen mode

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.


<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>
Enter fullscreen mode Exit fullscreen mode

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

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.


<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>
Enter fullscreen mode Exit fullscreen mode

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.


<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>
Enter fullscreen mode Exit fullscreen mode

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.

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.


<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>
Enter fullscreen mode Exit fullscreen mode

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.

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

Final application

Conclusion

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

Resources

Top comments (0)