This article will show you how to build a Temperature Converter with Next.js and Tailwind CSS as frontend and Strapi headless CMS as backend. You can find a link to the finished frontend code here, as well as the finished backend here
Prerequisite
Before starting this tutorial, you need to have
- Node.js installed on your local machine (v12 or v14) - Check this tutorial for instructions on how to install Node.js
- Basic understanding of Strapi - Get started with this quick guide
- Basic knowledge of Next.js
- Basic knowledge of Tailwind CSS
What is Next Js
Next.js is an awesome React framework for building highly dynamic applications. It comes with pre-rendering, automatic code-splitting amongst many other great features out of the box.
What is Tailwind CSS
Tailwind is a utility-first CSS framework for rapidly building custom user interfaces. By using Tailwind, we can write our css directly in our HTML classes. This leads to a faster development time.
What is Strapi
Strapi is a Node.js open-source headless CMS that allows us to develop APIs and manage content easily without the hassle of building out a project from scratch.
We can easily build out APIs faster and consume the contents via APIs using any REST API client or GraphQL.
Scaffolding a Strapi Project
To setup a new Strapi Project is quite straightforward and simple as running these few commands:
npx create-strapi-app temperature-converter --quickstart
Change temperature-converter
to the preferred name of your project
Nb: During the setup we would not be using any Strapi template
This will install and create a Strapi application and set up the project locally.
After installation, the browser would open a page on localhost:1337 which would prompt to set up first admin account to proceed with Strapi.
Building the Temperature collection
Next, we will create a new collection type that will store the details of each temperature converter.
In this article, we will create a collection type called ‘temperature’ that has these fields: from, to, multiplier, offset, offset_add
Clicking “Continue” would bring up another screen to select the fields for this collection. Choose the “Text” field from the list and provide from
as its name.
Click “Advanced settings” and check the “Required field” box to ensure this field is required when creating a new temperature converter.
We click on the Add another field to add the remaining fields. Below is a table showing the properties for each field in this collection:
Field Name | Field Type | Required |
---|---|---|
from | short text | true |
to | short text | true |
multiplier | number(decimal) | true |
offset | number(float) | true |
offset_add | number(decimal) | true |
Seeding Strapi
We would now need to seed our collection to have the predefined parameters we need in our formula.
To add data to the collection, we select the Temperature Collection on the left sidebar, click “Add New Temperature” and fill in the details.
After Seeding we would have the collection populated as such:
Allowing Public access
By default, whenever you create an API, Strapi creates six endpoints from the name given to the API. The endpoints generated for temperature should look like this:
By default, they’re all going to be restricted from public access. We need to tell Strapi that you’re okay with exposing these checked endpoints to the public. Go to Settings > Users & Permissions Plugin ****** > Roles and click to edit the Public Role. Next scroll down to permissions and check find
for Temperature.
This endpoint: http://localhost:1337/temperatures should now be available
Scaffolding a NextJs project
Create a Next.js app
To create a Next.js app, open your terminal, cd
into the directory you’d like to create the app in, and run the following command:
npx create-next-app -e with-tailwindcss nextjs-temperature
This would also configure Tailwind CSS with the project.
Run the Next.js development server
Next we cd
into the newly created directory, in our case that would be nextjs-temperature
cd nextjs-temperature
After which we start up the development server, by running this command:
npm run dev
If everything was set up fine, the next.js server should now be running on http://localhost:3000 we should get this shown on our browser:
Building NextJs Components
After setup we should have a folder structure such as this:
Next, we create the temperature converter page. We open the index.js
file in our favourite text editor, we delete its contents and replace them with the file content below:
import Head from 'next/head'
import { useState, useEffect } from 'react'
import { converter, fetchQuery, getUniqueTemp } from '../utils/helper'
export default function Home({ temperatures }) {
const [answer, setAnswer] = useState('')
const [from, setFrom] = useState('C')
const [to, setTo] = useState('F')
const [temperature, setTemperature] = useState(1)
const [options, setOptions] = useState([])
useEffect(() => {
setOptions(getUniqueTemp(temperatures, 'from'))
}, [temperatures])
const handleSubmit = (e) => {
e.preventDefault()
setAnswer(`${converter(temperatures, from, to, Number(temperature))} ${to}`)
}
return (
<div className="flex flex-col items-center justify-center min-h-screen py-2">
<Head>
<title>Temperature Converter</title>
<link rel="icon" href="/favicon.ico" />
</Head>
<main className="flex flex-col items-center justify-center w-full flex-1 px-20 text-center">
<h1 className="text-6xl font-bold">
Temperature {' '}
<a className="text-blue-600" href="https://nextjs.org">
Converter
</a>
</h1>
<form className="space-y-4 text-gray-700" onSubmit={handleSubmit}>
<div className="flex flex-wrap">
</div>
<div className="flex flex-wrap -mx-2 space-y-4 md:space-y-0">
<div className="w-full px-2 md:w-1/2">
<label className="block mb-1">From</label>
<select className="w-full h-10 pl-3 pr-6 text-base placeholder-gray-600 border rounded-lg appearance-none focus:shadow-outline" placeholder="Select From" value={from} onChange={ (e) => { setFrom(e.target.value)}}>
{options.map(e => (
<option value={e} key={e}>{e}</option>
))}
</select>
</div>
<div className="w-full px-2 md:w-1/2">
<label className="block mb-1" >To</label>
<select className="w-full h-10 pl-3 pr-6 text-base placeholder-gray-600 border rounded-lg appearance-none focus:shadow-outline" placeholder="Select To" value={to} onChange={ (e) => { setTo(e.target.value)}}>
{options.map(e => (
<option value={e} key={e}>{e}</option>
))}
</select>
</div>
</div>
<div className="flex flex-wrap -mx-2 space-y-4 md:space-y-0">
<div className="w-full px-2 md:w-1/2">
<label className="block mb-1">Value</label>
<input className="w-full h-10 px-3 text-base placeholder-gray-600 border rounded-lg focus:shadow-outline" type="number" value={temperature} onChange={ (e) => { setTemperature(e.target.value)}}/>
</div>
<div className="w-full px-2 md:w-1/2">
<input className="w-full h-10 px-3 my-7 cursor-pointer text-base placeholder-gray-600 border rounded-lg focus:shadow-outline hover:bg-blue-600 hover:text-white" type="submit" value="Convert"/>
</div>
</div>
</form>
<div>
Result is : {answer}
</div>
</main>
</div>
)
}
export async function getStaticProps() {
const temperatures = await fetchQuery('temperatures')
return {
props: {
temperatures
}
}
}
This file is the entry point to our app. We use it to create out a design for the app, and also set up our functionalities to handle the conversion process. We proceed to create a utils folder at the root of our project and create a file called helper.js
in it, this file would be where we place all helper functions.
This helper.js
file would contain basic helper functions that would help us convert the temperature as well as get data from the Strapi backend server.
const baseUrl = process.env.BASE_URL || 'localhost:1337'
export const converter = (temperatures, from, to, val) => {
let temperature = {}
let answer = 0
switch(`${from}-${to}`){
case 'F-C':
temperature = getFields(temperatures, from, to)
answer = (((val + temperature.offset) * temperature.multiplier ) + temperature.offset_add)
break;
case 'C-F':
temperature = getFields(temperatures, from, to)
answer = (((val + temperature.offset) * temperature.multiplier ) + temperature.offset_add)
break;
case 'C-K':
temperature = getFields(temperatures, from, to)
answer = (((val + temperature.offset) * temperature.multiplier ) + temperature.offset_add)
break;
case 'K-C':
temperature = getFields(temperatures, from, to)
answer = (((val + temperature.offset) * temperature.multiplier ) + temperature.offset_add)
break;
case 'F-K':
temperature = getFields(temperatures, from, to)
answer = (((val + temperature.offset) * temperature.multiplier ) + temperature.offset_add)
break;
case 'K-F':
temperature = getFields(temperatures, from, to)
answer = (((val + temperature.offset) * temperature.multiplier ) + temperature.offset_add)
break;
default:
answer = val
break;
}
return answer
}
export const getFields = (array, from, to) => {
return array.find(x => x.from === from && x.to === to)
}
export const fetchQuery = async (path, params = null) => {
let url
if (params !== null) {
url = `${baseUrl}/${path}/${params}`
} else {
url = `${baseUrl}/${path}`
}
const response = await fetch(`http://${url}`)
const data = await response.json()
return data
}
export const getUniqueTemp = (arr, key) => {
let keys = arr.map(function(item) { return item[key]; })
return [...new Set(keys)]
}
Finished App
The finished app looks like this:
Conclusion
And that's it, we saw how to set up a temperature converter using Strapi, Next.js and Tailwind CSS. This article can be taken further to be used as a base for other kinds of conversions. This goes to show what we can achieve using this setup and the possibilities therein.