Enhancing Data Retrieval in Next.js: Implementing Typed Requests and Responses with the Fetch API

Context I've been developing a web application using Next.js and a custom Django Python backend, but I am struggling to find an efficient approach for making API requests from my frontend to the backend. My main goal is to centralize the logic for fetching data while also implementing type safety for request bodies and responses.

Current Strategy

const api = {
  get: async function (url: string): Promise<any> {
    console.log("get", url);

    return new Promise((resolve, reject) => {
      fetch(`${process.env.NEXT_PUBLIC_API_BASE_URL}${url}`, {
        method: "GET",
        headers: {
          Accept: "application/json",
          "Content-Type": "application/json",
        },
        cache: "no-store",
      })
        .then((response) => response.json())
        .then((json) => {
          console.log("Response:", json);

          resolve(json);
        })
        .catch((error) => {
          reject(error);
        });
    });
  },
  post: async function (url: string, data: any): Promise<any> {
    console.log("post", url, data);

    return new Promise((resolve, reject) => {
      fetch(`${process.env.NEXT_PUBLIC_API_BASE_URL}${url}`, {
        method: "POST",
        body: JSON.stringify(data),
        headers: {
          Accept: "application/json",
          "Content-Type": "application/json",
        },
      })
        .then((response) => response.json())
        .then((json) => {
          console.log("Response:", json);

          resolve(json);
        })
        .catch((error) => {
          reject(error);
        });
    });
  },
}

While this approach centralizes the fetch logic for GET and POST requests, it lacks type safety as both methods accept and return data of type 'any'. The repetition of code for future methods such as PATCH and DELETE is also a concern.

Alternative Approach

export async function getProfile(id: string): Promise<Profile> {
  try {
    const response = (await api.get(getProfileRoute(id))) as Profile;
    return response;
  } catch (error) {
    console.log("Could not fetch profile...", error);
  }
}

This example offers more type safety by specifying the return type of the function and accepting an argument that can be typed to ensure correct data for a POST request.

Route Handlers After exploring Next.js documentation on route handlers route handlers, I found similar challenges in maintaining fetch logic without sacrificing type safety. Creating separate files for each HTTP method seems inefficient.

Question How can I implement a cleaner way to interact with my backend API from Next.js? The solution should:

  1. Make fetch logic reusable across different HTTP methods without constant repetition.
  2. Ensure proper typing for data sent in POST requests and received responses.

Answer №1

If you want to ensure type-safety when working with URLs, you can leverage the power of Template Literal Types and generics:

type User = {
    name: string;
}

type GetUserByIdUrl = `/users/${number}`;

type RequestMap = {
    "/users": User[];
    [k: GetUserByIdUrl]: User;
}

function httpGet<T extends keyof RequestMap>(url: T): Promise<RequestMap[T]> {
    // ...
};

httpGet("/users").then(res => {
    type test1 = typeof res; // User[]
});

httpGet("/users/1").then(res => {
    type test2 = typeof res; // User
});

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

The fusion of a Django model and modelform into a cohesive entity

My group of friends and I engage in a sports picking game using a rather inefficient spreadsheet system. To enhance my understanding of Django, I decided to develop a web application that simulates the game. Currently, I am working on the fundamentals of t ...

The React component fails to render when clicking on a Material-UI MenuItem

In my code, there is a simple mui Menu, where a MenuItem should trigger the rendering of another React component. The issue I am facing is that the Menu is being rendered in a separate file, which contains the definitions for the close and handleClick func ...

Defining the signature of an unnamed function in TypeScript

Within my Express code, I have an anonymous function set up like this: app.use((err, req, res, next) => { // ... }); I am looking to specify the type of the function as ErrorRequestHandler (not the return type). One way to achieve this is by defining ...

Error occurred due to 'NoneType' object lacking the 'split' attribute while attempting to send a post request through Ajax to Django

I recently created a method in my views.py file to serve as an API for user login using Django 2.1. The method works perfectly fine when tested with POSTMAN, but I encountered an issue when trying to use AJAX to send requests to the API. Instead of receivi ...

Is there a way to adjust the height of mat-sidenav-content to be 100%?

I'm having trouble scrolling down my mat-sidenav-content to reach the bottom where my pagination is located. When I try using fullscreen on mat-sidenav-container, my mat-toolbar disappears. How can I adjust my mat-sidenav-content based on the content? ...

The paths configuration in tsconfig.json is not functioning as anticipated

I've been encountering a module not found error while trying to work with jasmine-ts. To troubleshoot, I decided to use tsc and inspect my folder structure: \src\app\... \src\tests\... To address this issue, I created a ...

Avoiding circular imports in Angular modules

After restructuring my angular app from a monolithic shared feature module to smaller ones, I faced a challenge with what appears to be a cyclic dependency. The issue arises when I have components such as triggerA, modalA, triggerB, and modalB interacting ...

Tips for creating cascading dynamic attributes within Typescript?

I'm in the process of converting a JavaScript project to TypeScript and encountering difficulties with a certain section of my code that TypeScript is flagging as an issue. Within TypeScript, I aim to gather data in a dynamic object named licensesSta ...

Can you explain the distinction between interfaces containing function properties written as "f()" and "f: () =>"?

Let's take a look at two interfaces, A and B: interface A {f(): number} interface B {f: () => number} I have experimented with the following: var a: A = {f: function() {return 1}} var a: A = {f: () => 1} var b: B = {f: function() {return 1}} ...

Data is not being successfully transmitted through the ORM (Prisma) to the database

After spending hours trying to solve this issue, I've hit a roadblock. My current project is using Next.js 13. I managed to successfully connect my application to a Postgres Database, configure the Prisma schema, and test a migration with a seed.ts f ...

Is it correct to implement an interface with a constructor in TypeScript using this method?

I am completely new to TypeScript (and JavaScript for the most part). I recently came across the article discussing the differences between the static and instance sides of classes in the TypeScript handbook. It suggested separating the constructor into an ...

The JSX Configuration in TypeScript: Comparing ReactJSX and React

When working with Typescript and React, it's necessary to specify the jsx option in the compilerOptions section of the tsconfig.json file. Available values for this option include preserve, react, react-native, and react-jsx. { "compilerOptions": { ...

Steps for pulling information from the database and presenting it in a text box for users to edit

I'm struggling with a webpage that should allow users to update data from the database, but for some reason, the data is not displaying in the textbox. How can I make it show up correctly? Currently, on my webpage, users have to manually input all th ...

Error Encountered with Google Authentication in Localhost:3001 - Warning of 'Blocked Access'

Encountering a Next-auth Google authentication issue on my localhost development setup. Admin side (localhost:3000) and client side (localhost:3001) of e-commerce website are separate instances. Error message "access blocked" when trying Google authentica ...

Are there type declarations in TypeScript for the InputEvent?

Wondering if there is a @types/* module that offers type definitions for the InputEvent object? For more information about InputEvent, you can visit this link. ...

Encountered an issue with an invalid src prop ('link') on the `next/image` component. The hostname "localhost" has not been properly configured under images in your `next.config.js` file

I'm having an issue with the Image component in Next.js. I've provided the source URL like this: {`${API}/user/photo/${blog.postedBy.username}`} However, it's giving me an error. I even made changes to my next.config.js: module.exports = { ...

spawning a new process from a Django view

I am faced with a situation where I need to run a webservice that triggers a time-consuming process lasting up to a minute. My goal is to respond with a 204 status code, indicating successful reception of the request, while processing the task in the backg ...

Attempting to access the local variable 'content_type' before it has been assigned

The issue I am facing is related to the post function in my views.py. The problem arises when a POST form is submitted. UnboundLocalError at /pl/ local variable 'content_type' referenced before assignment I recently installed a new app for rati ...

What is the best way to set a JSON string as a variable?

I am attempting to send form input data to a REST service. Currently, the format is as follows: { "locationname":"test", "locationtype":"test", "address":"test" } However, the service is only accepting the following format: { "value": "{ loca ...

Vue.js - the lightweight JavaScript framework without any dependencies mentioned

I'm currently developing a project using Django (Python-based Web Framework) with Vue.js as the front-end framework. Most of the resources I've come across regarding Vue.js mention Node, especially for setting up the development server. I'm ...