Easily restore your project to a previous version with our new Instant One-click Backup Recovery

How to build a car dealership website with Hygraph

We'll use Hygraph to build a car dealership website, demonstrating how schemas are built and how data is retrieved from an API.
Subha Chanda

Subha Chanda

May 30, 2023
How to build a car dealership website with Hygraph

Building a content-rich application is a complicated task, especially when you’re using a modern tech stack. In some cases, you might need granular permissions support and custom workflows to localize your application. In others, you might want marketing content features that are intuitive for non-technical users, or you might need multi-tenancy support for lots of external content creators.

With a modern content platform like Hygraph, it’s a simple matter to tackle all of the above. Essentially a GraphQL content platform, Hygraph allows you to easily create custom schemas with its low-code schema builder and then access its stored data via the GraphQL API.

Let’s take Hygraph for a test run for the next few minutes, and see what it takes to build a car dealership website using Hygraph as a backend. This tutorial demonstrates how schemas are made and how easy it is to fetch data from a third-party API source with Hygraph. We’ll build the application frontend using Next.js.

#Building a car dealership website using Hygraph

Before getting into the nuts and bolts of how to build a car dealership website, you need to have some prerequisites in place:

  • A basic understanding of Next.js and Node.js
  • Node.js and NPM installed on your machine
  • A Hygraph account
  • An Auto.dev account to fetch car details

Obtaining an Auto.dev API endpoint

Let’s start by obtaining the Auto.dev API keys and endpoint. Auto.dev is an automotive API provider that facilitates accessing vehicle data, such as the VIN, or listing all vehicles filtered according to your parameters. It’s simple to use and free.

Navigate to Auto.dev and create a new account by clicking Get your free API key. After creating an account, you can obtain the API endpoint and the API key from the dashboard.

undefined

Keep this tab open or save the keys somewhere—you’ll need them later.

Creating a Hygraph account

Visit the Hygraph website and click Get Started. After successfully signing up on the registration page, you can either add a project or choose a template.

For this tutorial, add a new project from scratch. Click Add Project and fill out the form with the necessary details, like the name of your project, a description, and the data storage region.

undefined

When your details are to your satisfaction, click Add Project to view a menu like this:

  • Schema. Allows you to create custom schemas from the intuitive GUI of Hygraph.
  • Content. Lets you add content to these schemas.
  • Assets. Lets you manage static assets like images.
  • API Playground. Enables you to explore the GraphQL API of your project from the dashboard.

undefined

Schemas are the heart of Hygraph. They let you create content models, add remote sources, and create enumerators depending on your preferences. So let’s create a few.

Creating schemas in Hygraph

Click Schema in the menu. You can create any type of schema you want, but for this tutorial, let’s create a model for blog posts.

Click Add next to the Models option. In the modal that opens, enter a few details about the model, like its name, the API ID you'll use to call the model, the plural form of the model, and its description.

undefined

After successfully creating the model, add the fields that this particular model offers: title, summary, and the post itself.

undefined

If you click Content from the menu at this point, you should see an option to add a new blog post.

Now that you know how to create a basic schema, let’s add a remote source for fetching vehicle details.

Adding Remote Sources to Hygraph and creating a vehicle schema

Hygraph provides a feature called Content Federation, which integrates external data sources into your Hygraph account. This enables you to easily access all your data from a single GraphQL endpoint.

This tutorial integrates the Auto.dev data source in order to access the vehicle data from the Hygraph endpoint itself. In other words, when an admin inputs a VIN of a particular vehicle into the Hygraph CMS, all the data available in Auto.dev for that vehicle can be retrieved from the GraphQL endpoint.

To achieve this, let’s add a remote source. Click Schema > Components > Add from the menu, and a new form opens up. Fill in the basic details like the name and description of the remote source, and then choose the type of source. This tutorial selects REST.

Add the base URL of the Auto.dev endpoint, which in this case is https://auto.dev/api/vin/. Add the Authorization header to authorize your requests, and add the Authorization key and the value of Bearer API_KEY so that it looks like this:

undefined

Replace API_KEY with the key that you received from the Auto.dev dashboard. You can also define custom type definitions for the API data, but let’s skip adding it for this tutorial. Click Add, and your remote source is ready.

You can now create a model to use the remote source. Click Add next to the Models option, and name it Car Detail. This model holds the car's name, a gallery for images, a description, price, and condition, along with the car details received from the API.

Add a single-line field called VIN to pass to the remote source to decode the car detail, select the REST option from the model’s sidebar, and name it something appropriate, like car info for example.

undefined

Follow this pattern to fill out the rest of the form:

  • Remote source. This option lets you choose which remote source you’re referencing here in case you have multiple sources. The Auto.dev API is accessible through GET request only, so there’s no need to change the default.
  • Input arguments. This field allows you to pass input arguments in the API call.
  • Path. This option allows you to append path data to the base URL. In this case, you need to append the VIN to the base URL. Using {{doc.vin}} sets the VIN of the document as a path variable. The doc object allows you to reference any field from the model. Because the API ID of VIN is vin, it’s referenced as doc.vin.

The complete model is shown below.

undefined

Adding content to Hygraph

With your model for adding car details ready, let’s add some content from the Hygraph content menu.

Click Content > Car Detail from the content menu, and then Add Entry. Let’s say you want to add a Porsche 911. The VIN of this particular model is WP0AB2A93KS114315. Fill in the form and click Save, then Publish to publish your content.

undefined

Editor's Note

You can also add a few dummy blog posts to display in the frontend via Hygraph’s Blog Posts schema.

Setting Hygraph permissions

Once you’ve added as many vehicles as you like, let’s set up Hygraph permissions to access the data in your application.

Hygraph provides you with the required endpoints, but by default, you can’t access them without setting proper permissions. To make the endpoints accessible, click Project Settings > Access > API Access. The content API endpoint is the endpoint for accessing the data in this case. You’ll need this string later.

To create a permanent auth token to use for accessing the data, scroll down the API Access page and click Add Token under under the Permanent Auth Token.

undefined

Give the token a name and a description, set the stage as Published, and click Add & configure permissions. On the permissions page, copy the bearer token. This tutorial leaves the values in the Content API section set to their default.

undefined

Your Hygraph setup is complete, but keep a note of the content endpoint and the bearer token. You’ll need them to connect the frontend.

Designing the application’s frontend

If you’re unfamiliar with Next.js, their documentation is a good place to start. If you’re already familiar with Next.js, you can jump directly to the code for this tutorial in GitHub.

Creating a new Next.js project is simple. Type the following command in the terminal:

npx create-next-app@latest APP_NAME

Replace APP_NAME with the name of your application. To keep this article simple, choose options similar to the image below when configuring the template.

undefined

Editor's Note

Note that this tutorial doesn’t use TypeScript or any other styling library like Tailwind.

Create a components folder in the root directory and add a new file called CarCard.jsx for displaying the car details on the homepage. Paste the following code in this component:

import styles from '@/styles/Car.module.css';
import Image from 'next/image';
import Link from 'next/link';
const Car = ({ id, imageUrl, description, name, make, price, condition }) => {
return (
<div className={styles.container}>
<Image
src={imageUrl}
alt={`${make} ${name}`}
className={styles.image}
width={200}
height={200}
/>
<div className={styles.description}>
<h2 className={styles.name}>{name}</h2>
<p className={styles.make}>{make}</p>
<p className={styles.details}>{description}</p>
<p className={styles.price}>Price: {price}</p>
<p className={styles.condition}>Condition: {condition}</p>
<button className={styles.button}>
<Link href={`/${id}`}>View Details</Link>
</button>
</div>
</div>
);
};
export default Car;

This component takes a few props for displaying the image, description, name, price, condition and the make of the car.

Create a new file called Car.module.css inside the styles folder and paste the following code inside it:

.container {
display: flex;
flex-direction: row;
align-items: center;
margin-bottom: 20px;
}
.image {
width: 200px;
height: 150px;
margin-right: 20px;
object-fit: cover;
border-radius: 5px;
}
.description {
display: flex;
flex-direction: column;
}
.name {
font-size: 1.5rem;
font-weight: bold;
margin: 0;
}
.make {
font-size: 1rem;
color: gray;
margin: 5px 0;
}
.details {
font-size: 1rem;
margin: 0;
}
.price {
font-size: 1rem;
color: gray;
}
.condition {
font-size: 1rem;
color: gray;
}
.button {
width: 7rem;
}

And your car card is ready. To display the details of the car on a separate page, create a new file called [id].jsx inside the pages folder. This page will work as a dynamic route.

Paste the following code inside this route:

import Image from 'next/image';
import styles from '@/styles/CarDetails.module.css';
import { useRouter } from 'next/router';
import { useQuery, gql } from '@apollo/client';
const CarDetails = () => {
const router = useRouter();
const { id } = router.query;
return (
<div className={styles.container}>
{loading ? (
<p>Loading...</p>
) : (
<div>
<div className={styles.imageContainer}>
<Image
src={values.gallery[0].url}
alt={`${values.carInfo.make.name} ${values.carInfo.model.name}`}
className={styles.image}
height={400}
width={400}
/>
</div>
<div className={styles.detailsContainer}>
<h1
className={styles.name}
>{`${values.carInfo.make.name} ${values.carInfo.model.name}`}</h1>
<p className={styles.doors}>
Number of Doors: {values.carInfo.numOfDoors}
</p>
<p className={styles.model}>Make: {values.carInfo.model.name}</p>
<p className={styles.model}>
Engine Compressor Type: {values.carInfo.engine.compressorType}
</p>
<div className={styles.description}>
<h2>Description</h2>
<p>{values.description}</p>
</div>
</div>
</div>
)}
</div>
);
};
export default CarDetails;

Create a new file called CarDetails.module.css under the styles folder and paste in the following code:

.container {
display: flex;
flex-direction: row;
align-items: center;
margin-bottom: 20px;
}
.imageContainer {
flex-basis: 40%;
margin-right: 20px;
}
.image {
width: 100%;
height: auto;
object-fit: cover;
border-radius: 5px;
}
.detailsContainer {
flex-basis: 60%;
}
.name {
font-size: 2rem;
margin: 0;
}
.makeYear {
font-size: 1.5rem;
color: gray;
margin: 10px 0;
}
.model {
font-size: 1.5rem;
margin: 0;
}
.transmission {
font-size: 1rem;
color: gray;
margin-bottom: 20px;
}
.description {
margin-top: 20px;
}
.description h2 {
font-size: 1.5rem;
margin: 0;
}
.description p {
font-size: 1rem;
margin: 10px 0;
}

Editor's Note

We are going to assume you’re already familiar with CSS and React or Next.js, so this tutorial doesn’t go in-depth about styles and basic JSX code.

Add media.graphassets.com to your next.config.js file, or else your images won’t display. Replace your next.config.js file contents with:

/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
images: {
domains: ['media.graphassets.com'],
},
};
module.exports = nextConfig;

With design complete, let’s connect Hygraph with your application.

Connecting Hygraph with the frontend

To connect Hygraph with the frontend, first install two dependencies—graphql and @apollo/client—by running the following command:

npm i @apollo/client graphql

Create a new file called .env.local in the root directory and save the Hygraph endpoint and the bearer token as follows:

NEXT_PUBLIC_HYGRAPH_ENDPOINT=HYGRAPH_PUBLIC_CONTENT_ENDPOINT
NEXT_PUBLIC_HYGRAPH_PERMANENTAUTH_TOKEN=HYGRAPH_PERMANENT_AUTH_TOKEN

Replace HYGRAPH_PUBLIC_CONTENT_ENDPOINT and HYGRAPH_PERMANENT_AUTH_TOKEN with the tokens from your Hygraph dashboard.

Create a new folder called graphql and create a file called client.js inside it. Set up the Hygraph client as follows:

import { ApolloClient, createHttpLink, InMemoryCache } from '@apollo/client';
import { setContext } from '@apollo/client/link/context';
const httpLink = createHttpLink({
uri: process.env.NEXT_PUBLIC_HYGRAPH_ENDPOINT,
});
const authLink = setContext((_, { headers }) => {
return {
headers: {
...headers,
authorization: `Bearer ${process.env.NEXT_PUBLIC_HYGRAPH_PERMANENTAUTH_TOKEN}`,
},
};
});
export const client = new ApolloClient({
link: authLink.concat(httpLink),
cache: new InMemoryCache(),
});

In the previous code, note that:

  • httpLink sets the base URL of the API.
  • authLink sets the authorization with the bearer token received from the Hygraph backend.
  • The client is created by passing the authLink and the httpLink inside ApolloClient.

Open the _app.js file of your project and replace the existing code with the following:

import '@/styles/globals.css';
import { ApolloProvider } from '@apollo/client';
import { client } from '@/graphql/client';
export default function App({ Component, pageProps }) {
return (
<ApolloProvider client={client}>
<Component {...pageProps} />
</ApolloProvider>
);
}

The component is wrapped using the ApolloProvider, and the client is passed as a prop. You can access the custom hooks defined at @apollo/client from anywhere in the app now.

Let’s write a GraphQL query to get all the car details. Open the index.js file and import the following:

import { useEffect, useState } from 'react';
import { gql, useQuery } from '@apollo/client';
import CarCard from '@/components/CarCard';

The useQuery hook allows you to run the queries written using gql. The useQuery hook returns three values: loading, error, and data. Until the data or an error is received, the loading value stays true. The error throws an error if there’s any, otherwise, it’s undefined, and the data holds all the data received from the endpoint.

Replace the content of Home component with the following:

export default function Home() {
const [price, setPrice] = useState('all');
const { loading, error, data } = useQuery(gql`
query MyQuery {
carDetails(where: {
${price === 'moreThan4' ? 'price_gt: 400000' : ''}
${price === 'lessThan4' ? 'price_lt: 400000' : ''}
${price === 'all' ? 'price_gt: 0' : ''}
}) {
id
description
gallery {
url
}
nameOfTheCar
price
condition
}
}
`);
const options = [
{ value: 'all', label: 'All' },
{ value: 'moreThan4', label: '> 400000' },
{ value: 'lessThan4', label: '< 400000' },
];
const onChange = (e) => {
setPrice(e.target.value);
console.log(e.target.value);
};
return (
<>
<Head>
<title>Create Next App</title>
<meta name='description' content='Generated by create next app' />
<meta name='viewport' content='width=device-width, initial-scale=1' />
<link rel='icon' href='/favicon.ico' />
</Head>
<main>
<div>
<label htmlFor='price'>Filter By Price:</label>
<select id='price' value={price} onChange={onChange}>
{options.map((option) => (
<option key={option.value} value={option.value}>
{option.label}
</option>
))}
</select>
</div>
{loading ? (
<p>Loading...</p>
) : (
data.carDetails.map((car) => (
<CarCard
key={car.id}
imageUrl={car.gallery[0].url}
description={car.description}
name={car.nameOfTheCar}
price={car.price}
condition={car.condition}
id={car.id}
/>
))
)}
</main>
</>
);
}

The logic is straightforward here. The page has a simple select field to filter the cars. There are three view options inside the folder:

  • All cars
  • Cars that have a price lower than $400,000
  • Cars with a price higher than $400,000

Depending on the data, the CarCard component is rendered in a loop. For each car, the details are passed as a prop to it. A React state is defined to store the price of the chosen select value to filter cars. By default, the select field will have all as the value.

Run a query using the useQuery hook and gql.

query MyQuery {
carDetails(where: {
${price === 'moreThan4' ? 'price_gt: 400000' : ''}
${price === 'lessThan4' ? 'price_lt: 400000' : ''}
${price === 'all' ? 'price_gt: 0' : ''}
}) {
id
description
gallery {
url
}
nameOfTheCar
price
condition
}
}
`);

The MyQuery has a where condition. Depending on the chosen select value, it shows cars that cost more or less than $400,000. If the select field value is all, it shows cars that cost more than $0. This query returns the ID, description, gallery image URLs, name, price, and condition of the car.

The options for the select field are hard-coded for now. The onChange function that sets the price state is defined.

Inside the return statement, the options are mapped to display them inside the select field. If loading is true, it shows a loading message, otherwise, it maps the data received from the API and passes it inside the prop of the CarCard component.

The homepage is ready, so let’s move on to the car details page, where you’ll use the data received from the remote source.

Depending on the ID of the route, let’s fetch the data from the API. Write a new GraphQL query and extract the values from there. Paste this code above the return statement:

const query = gql`
query carDetail_carInfo($id: ID!) {
values: carDetail(where: { id: $id }) {
nameOfTheCar
description
gallery {
url
}
carInfo
}
}
`;
const { loading, error, data } = useQuery(query, { variables: { id } });
const { values } = loading ? { values: {} } : data;

This is similar to the query used for the homepage. The main difference is that the variable is the id of the Hygraph content ID here, and another value called carInfo is fetched from the endpoint. This carInfo endpoint returns the data received from the remote source.

If you take a look at the code for this page, you can see that the car make, model, engine, number of doors, and a few other data are fetched directly from the remote source using the Hygraph GraphQL endpoint.

undefined

Your application is now ready to test. Run the code with the npm run dev command from your terminal. This starts the Next.js server on http://localhost:3000. The following GIF demonstrates the application in action.

undefined

Your car dealership website is ready for the world!

Editor's Note

Remember that you can check out the complete code for this tutorial on GitHub.

#What's next

Hygraph is a headless CMS that allows you to create highly customizable and powerful applications without worrying about the backend stack. You got to explore this functionality just now building a custom website with Hygraph and Next.js specifically for a hypothetical car dealership. This tutorial used the Auto.dev API as a remote data source to fetch information about car models via their VINs; the remote source response was directly accessible through Hygraph’s GraphQL endpoint.

Ready to build your own scalable content experience? Create a free-forever account right here.

Blog Author

Subha Chanda

Subha Chanda

Subha Chanda is a freelance web developer who's passionate about learning and experimenting with new things. He loves to write about what he's found out.