Part 3: How to Build a NextJS App using Typescript and TailwindCSS: The Complete Guide

Part 3: How to Build a NextJS App using Typescript and TailwindCSS: The Complete Guide

Dynamic routing and API endpoints in NextJS

Subscribe to my newsletter and never miss my upcoming articles

Listen to this article

Hello Everyone šŸ‘‹ I'm Abhinav Rajesh and this is 3rd Part of the series "How to Build a NextJS App using Typescript and TailwindCSS: The Complete Guide" where we would be learning about creating dynamic routes and how to setup API endpoints in NextJS.

Previous Parts of Series

  1. Part 1: Setting up NextJS with Typescript and TailwindCSS
  2. Part 2: Fetching and displaying data from API in NextJS using getStaticProps, TailwindCSS @apply directive and Typescript Interface.

In the last article, we discussed Routing in NextJS, interfaces in TypeScript and @apply directives of TailwindCSS. In case you missed the article I would recommend checking them out first.

In this article, we would be going through a bunch of stuff like

Dynamic Routing

In the last article, we ended it when we displayed all the articles on the homepage. image.png

Now we would be creating a page to display the article when clicked by the user. For that create a folder inside the pages folder and name it article and inside the article folder create a new file [id].tsx.

Your folder structure should look something like this

pages/
ā”£ api/
ā”ƒ ā”— hello.ts
ā”£ article/
ā”ƒ ā”— [id].tsx
ā”£ index.tsx
ā”— _app.tsx

Now inside article/[id].tsx add the following code

// pages/article/[id].tsx
import { GetServerSideProps, InferGetServerSidePropsType } from "next";
import Link from "next/link";
import Navbar from "../../components/Navbar";
import { Articles } from "../../types";

const Article = ({
  article,
}: InferGetServerSidePropsType<typeof getServerSideProps>) => {
  return (
    <div className="max-w-5xl mx-auto">
      <Navbar />
      <div className="flex flex-col items-start justify-center mt-40">
        <h1 className="font-bold text-4xl capitalize mb-4">{article.title}</h1>
        <p className="text-xl">{article.body}</p>
        <Link href="/">
          <a className="py-2 px-4 bg-blue-600 hover:bg-blue-500 text-white rounded mt-4">
            Go Back
          </a>
        </Link>
      </div>
    </div>
  );
};

export const getServerSideProps: GetServerSideProps = async (context) => {
  const res = await fetch(
    `https://jsonplaceholder.typicode.com/posts/${context.params!.id}`
  );
  const article: Articles = await res.json();

  return {
    props: {
      article: article,
    },
  };
};

export default Article;

Here in the above code, we are fetching the data from a fake server according to the id of the post the user clicked on and displaying the result.

Now when we click on any article, it should show some page similar to this

image.png

But there is a small issue affecting the performance here. That is, each time we are sending a request to the article it fetches from the API and displays the result after server-side rendering. To make it more performant what we can do is to change the Server-side fetching to static fetching. So what basically happens is it creates the page in build time and returns the page when requested! [More on this on Part 2 of this series] So change the code to following

// pages/article/[id].tsx
import { GetStaticPaths, GetStaticProps, InferGetStaticPropsType } from "next";
import Link from "next/link";
import Navbar from "../../components/Navbar";
import { Articles } from "../../types";

const Article = ({
  article,
}: InferGetStaticPropsType<typeof getStaticProps>) => {
  return (
    <div className="max-w-5xl mx-auto">
      <Navbar />
      <div className="flex flex-col items-start justify-center mt-40">
        <h1 className="font-bold text-4xl capitalize mb-4">{article.title}</h1>
        <p className="text-xl">{article.body}</p>
        <Link href="/">
          <a className="py-2 px-4 bg-blue-600 hover:bg-blue-500 text-white rounded mt-4">
            Go Back
          </a>
        </Link>
      </div>
    </div>
  );
};

export const getStaticProps: GetStaticProps = async (context) => {
  const res = await fetch(
    `https://jsonplaceholder.typicode.com/posts/${context.params!.id}`
  );
  const article: Articles = await res.json();

  return {
    props: {
      article: article,
    },
  };
};

export const getStaticPaths: GetStaticPaths = async () => {
  const res = await fetch(`https://jsonplaceholder.typicode.com/posts`);
  const articles: Articles[] = await res.json();

  const ids = articles.map((article) => article.id);

  const paths = ids.map((id) => ({
    params: {
      id: id.toString(),
    },
  }));
  return {
    paths: paths,
    fallback: false,
  };
};

export default Article;

Here the getStaticPaths specifies all the dynamic routes to pre-render pages based on data. And getStaticProps fetches the data at build time.

API in NextJS

Your NextJS app can be used as an API! If you visit the http://localhost:3000/api/hello, you would be seeing the following endpoint which returns a json object with name as key and John Doe as value

api/hello endpoint

Let's create an endpoint to replace the fake server which we were using. In the root directory, create a file named data.ts and add the code from my here or copy the following code

// data.ts
export const data = [
  {
    userId: 1,
    id: 1,
    title:
      "sunt aut facere repellat provident occaecati excepturi optio reprehenderit",
    body: "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto",
  },
  {
    userId: 1,
    id: 2,
    title: "qui est esse",
    body: "est rerum tempore vitae\nsequi sint nihil reprehenderit dolor beatae ea dolores neque\nfugiat blanditiis voluptate porro vel nihil molestiae ut reiciendis\nqui aperiam non debitis possimus qui neque nisi nulla",
  },
  {
    userId: 1,
    id: 3,
    title: "ea molestias quasi exercitationem repellat qui ipsa sit aut",
    body: "et iusto sed quo iure\nvoluptatem occaecati omnis eligendi aut ad\nvoluptatem doloribus vel accusantium quis pariatur\nmolestiae porro eius odio et labore et velit aut",
  },
  {
    userId: 1,
    id: 4,
    title: "eum et est occaecati",
    body: "ullam et saepe reiciendis voluptatem adipisci\nsit amet autem assumenda provident rerum culpa\nquis hic commodi nesciunt rem tenetur doloremque ipsam iure\nquis sunt voluptatem rerum illo velit",
  },
  {
    userId: 1,
    id: 5,
    title: "nesciunt quas odio",
    body: "repudiandae veniam quaerat sunt sed\nalias aut fugiat sit autem sed est\nvoluptatem omnis possimus esse voluptatibus quis\nest aut tenetur dolor neque",
  },
  {
    userId: 1,
    id: 6,
    title: "dolorem eum magni eos aperiam quia",
    body: "ut aspernatur corporis harum nihil quis provident sequi\nmollitia nobis aliquid molestiae\nperspiciatis et ea nemo ab reprehenderit accusantium quas\nvoluptate dolores velit et doloremque molestiae",
  },
  {
    userId: 1,
    id: 7,
    title: "magnam facilis autem",
    body: "dolore placeat quibusdam ea quo vitae\nmagni quis enim qui quis quo nemo aut saepe\nquidem repellat excepturi ut quia\nsunt ut sequi eos ea sed quas",
  },
  {
    userId: 1,
    id: 8,
    title: "dolorem dolore est ipsam",
    body: "dignissimos aperiam dolorem qui eum\nfacilis quibusdam animi sint suscipit qui sint possimus cum\nquaerat magni maiores excepturi\nipsam ut commodi dolor voluptatum modi aut vitae",
  },
  {
    userId: 1,
    id: 9,
    title: "nesciunt iure omnis dolorem tempora et accusantium",
    body: "consectetur animi nesciunt iure dolore\nenim quia ad\nveniam autem ut quam aut nobis\net est aut quod aut provident voluptas autem voluptas",
  },
  {
    userId: 1,
    id: 10,
    title: "optio molestias id quia eum",
    body: "quo et expedita modi cum officia vel magni\ndoloribus qui repudiandae\nvero nisi sit\nquos veniam quod sed accusamus veritatis error",
  },
];

Now inside the api folder create an articles folder and add 2 file, index.ts and [id].ts.

Inside the index.ts add the following code

// pages/api/articles/index.ts
import { NextApiRequest, NextApiResponse } from "next";
import { data } from "../../../data";

export default function handler(_req: NextApiRequest, res: NextApiResponse) {
  const articles = data;
  if (articles.length > 0) return res.status(200).json(articles);
  return res
    .status(500)
    .json({ message: `Error fetching the articles. Please try again later.` });
}

We fetch all the articles from the data.ts file which we created just now and sends the article as a response to the request.

If you visit the endpoint http://localhost:3000/api/articles you could see all the articles present there

Now in api/articles/[id].ts file add the following code

// pages/api/articles/[id].ts
import { NextApiRequest, NextApiResponse } from "next";
import { data } from "../../../data";

export default function handler(
  { query: { id } }: NextApiRequest,
  res: NextApiResponse
) {
  const article = data.filter((article) => article.id.toString() === id);
  if (article.length > 0) return res.status(200).json(article[0]);
  return res.status(404).json({ message: `Article with id: ${id} not found!` });
}

Here it fetches the data from the data.ts file and returns the article which has the same id as in the query

Using the API instead of the Fake Server

Instead of using the fake server, we can use the API endpoint created by us just now. So, head over to pages/index.tsx file and change the url inside the getStaticProps function to /api/articles

// pages/index.tsx

export const getStaticProps = async () => {
  const articles: Articles[] = await (await fetch("/api/articles")).json();

  return {
    props: {
      articles,
    },
  };
};

This would give us an error saying TypeError: Only absolute URLs are supported, so what we have to do now is, create a constants.ts file in the root directory and add the following code

const __prod__ = process.env.NODE_ENV === "production";
export const API_URL = __prod__ ? "" : "http://localhost:3000/api";

Here, this file checks if it's production, if it is it should point to the production URL (Which we haven't added yet, we would add it when we deploy. For now leave it as an empty string) else it would point to localhost:3000/api

Now, change the function to following would fix the problem

// pages/index.tsx
export const getStaticProps = async () => {
  const articles: Articles[] = await (
    await fetch(`${API_URL}/articles`)
  ).json();

  return {
    props: {
      articles,
    },
  };
};

And in pages/article/[id].tsx change the functions to following to point to our new URL

// pages/article/[id].tsx
export const getStaticProps: GetStaticProps = async (context) => {
  const res = await fetch(`${API_URL}/articles/${context.params!.id}`);
  const article: Articles = await res.json();

  return {
    props: {
      article: article,
    },
  };
};

export const getStaticPaths: GetStaticPaths = async () => {
  const res = await fetch(`${API_URL}/articles`);
  const articles: Articles[] = await res.json();

  const ids = articles.map((article) => article.id);

  const paths = ids.map((id) => ({
    params: {
      id: id.toString(),
    },
  }));
  return {
    paths: paths,
    fallback: false,
  };
};

Now the app should work as before, but this time we are fetching the data from the API we created! šŸŽ‰

That's it for this article. In the next article, we would be looking at Head by NextJS and we would be deploying the application to Vercel. Until then stay tuned!

As always, linking the Github Repository.

Support šŸ™Œ

If you're enjoying my articles, consider supporting me with a coffee ā˜•ļø or upvoting the articles. It really motivates me to keep going.

Buy Me A Coffee

Lets connect šŸŒŽ

Github
Twitter
LinkedIn

Feedback šŸŽø

Feedback helps to improve my articles. I'd love to hear feedback and thoughts on the article. Looking forward to your views.

Ā 
Share this