how to send POST request in TypeScript - Photo by Nick Karvounis on Unsplash

How to Send POST Requests in TypeScript: Complete Guide with Examples

Executive Summary

Sending POST requests in TypeScript is one of the most fundamental operations you’ll perform when building modern applications, whether you’re working with REST APIs, webhooks, or microservices. The core challenge isn’t complexity—it’s choosing the right approach and handling the edge cases that trip up developers: network timeouts, malformed JSON, authentication headers, and connection failures.

This guide walks you through three proven methods: the browser’s native fetch API, the Axios HTTP client, and Node.js’s built-in HTTP module. We’ll cover error handling patterns, type safety with TypeScript, and production-ready implementations. Last verified: April 2026.

Main Data Table

Method Best For Built-in Type Safety Error Handling
Fetch API Browser environments, modern Node.js Yes (Node 18+) Excellent with TypeScript Promise-based, requires manual checks
Axios Complex requests, interceptors, retries No (npm install) Native TypeScript support Automatic rejection on error status
Node.js HTTP Legacy systems, minimal dependencies Yes (built-in) Manual typing required Event-based, verbose
Got Library High-performance server applications No (npm install) Excellent TypeScript support Automatic retries, timeout handling

Breakdown by Experience Level

Different approaches work best depending on your experience and use case. Beginners typically find fetch easiest to understand—it’s just a promise-based function call. Intermediate developers often prefer Axios for its cleaner syntax and better error handling. Advanced developers might choose Got or the native HTTP module for performance-critical applications.

Beginner (Fetch): Simple, built-in to modern browsers and Node.js 18+. Great for learning HTTP fundamentals.

Intermediate (Axios): Popular npm package with request/response interceptors, automatic JSON serialization, and better error messages.

Advanced (Got/Node HTTP): Full control over socket management, connection pooling, and streaming for large payloads.

Comparison Section

Feature Fetch Axios Got Node HTTP
Setup required None npm install axios npm install got None
Auto JSON serialization No Yes Yes No
Rejects on error status No (manual) Yes (2xx+ only) Yes (2xx+ only) No (manual)
Request interceptors No Yes Yes (hooks) No
Built-in retry logic No No (libraries available) Yes No
TypeScript first-class Good Excellent Excellent Basic

Key Factors When Sending POST Requests

1. Choosing the Right Content-Type Header

Most POST requests send JSON data, which requires the Content-Type: application/json header. However, form submissions use application/x-www-form-urlencoded, and file uploads need multipart/form-data. This matters because servers parse your request body differently based on this header. Setting it incorrectly is one of the top reasons POST requests fail silently.

2. Proper Error Handling Architecture

Network requests fail in multiple ways: DNS resolution errors, timeouts, server errors (5xx), client errors (4xx), and malformed responses. You need different handling strategies for each. A try-catch block isn’t enough—you also need to check the response status, validate JSON parsing, and implement exponential backoff for retries. Ignoring these is the second most common mistake we see.

3. TypeScript Type Safety for Request/Response Bodies

TypeScript’s strength is catching errors at compile time. Define interfaces for your request and response payloads, then use generic type parameters in your HTTP functions. This prevents runtime errors when the API returns unexpected fields or the server changes its contract without warning.

4. Authentication and Authorization Headers

Most production APIs require some form of authentication: Bearer tokens, API keys, Basic auth, or OAuth. These must be added to the request headers before sending. Never hardcode credentials in source code—always use environment variables or secure credential stores. Many developers forget to include these headers or include them incorrectly, causing cryptic 401 Unauthorized errors.

5. Request Timeouts and Connection Limits

Without timeout configuration, a hung server can freeze your entire application. Set reasonable timeouts (typically 10-30 seconds for API calls). Also consider connection pooling limits to prevent resource exhaustion when making many concurrent requests.

Practical Code Examples

Using Fetch API

// Basic fetch POST with error handling
interface ApiResponse {
  id: number;
  status: string;
  createdAt: string;
}

interface RequestPayload {
  name: string;
  email: string;
}

async function sendPostWithFetch(
  url: string,
  data: RequestPayload
): Promise<ApiResponse> {
  try {
    const response = await fetch(url, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${process.env.API_TOKEN}`,
      },
      body: JSON.stringify(data),
    });

    // Fetch doesn't reject on 4xx/5xx - check status manually
    if (!response.ok) {
      throw new Error(`HTTP ${response.status}: ${response.statusText}`);
    }

    const result: ApiResponse = await response.json();
    return result;
  } catch (error) {
    if (error instanceof TypeError) {
      throw new Error(`Network error: ${error.message}`);
    }
    throw error;
  }
}

Using Axios (Recommended for Most Projects)

import axios, { AxiosError } from 'axios';

interface ApiResponse {
  id: number;
  status: string;
  createdAt: string;
}

interface RequestPayload {
  name: string;
  email: string;
}

// Create instance with defaults
const apiClient = axios.create({
  baseURL: 'https://api.example.com',
  timeout: 10000,
  headers: {
    'Authorization': `Bearer ${process.env.API_TOKEN}`,
  },
});

async function sendPostWithAxios(
  endpoint: string,
  data: RequestPayload
): Promise<ApiResponse> {
  try {
    const response = await apiClient.post<ApiResponse>(
      endpoint,
      data
    );
    return response.data;
  } catch (error) {
    if (axios.isAxiosError(error)) {
      const axiosError = error as AxiosError;
      if (axiosError.response) {
        // Server responded with error status
        console.error(`Server error: ${axiosError.response.status}`);
      } else if (axiosError.request) {
        // Request made but no response
        console.error('No response from server');
      } else {
        // Error in request setup
        console.error(`Error: ${axiosError.message}`);
      }
    }
    throw error;
  }
}

// Usage
const result = await sendPostWithAxios('/users', {
  name: 'John Doe',
  email: 'john@example.com',
});

Using Node.js Native HTTP Module

import https from 'https';

interface RequestPayload {
  name: string;
  email: string;
}

function sendPostWithHttp(
  url: string,
  data: RequestPayload
): Promise<string> {
  return new Promise((resolve, reject) => {
    const payload = JSON.stringify(data);

    const options = {
      hostname: 'api.example.com',
      port: 443,
      path: '/endpoint',
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Content-Length': Buffer.byteLength(payload),
        'Authorization': `Bearer ${process.env.API_TOKEN}`,
      },
      timeout: 10000,
    };

    const req = https.request(options, (res) => {
      let data = '';

      res.on('data', (chunk) => {
        data += chunk;
      });

      res.on('end', () => {
        if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) {
          resolve(data);
        } else {
          reject(new Error(`HTTP ${res.statusCode}: ${data}`));
        }
      });
    });

    req.on('error', reject);
    req.on('timeout', () => {
      req.destroy();
      reject(new Error('Request timeout'));
    });

    req.write(payload);
    req.end();
  });
}

Historical Trends

Five years ago, developers relied heavily on the jQuery $.ajax() method or the Node.js request library (now deprecated). Around 2020-2021, Fetch API gained wider browser support and became standard in Node.js 18+. This shift simplified codebases significantly—no external dependencies needed for basic HTTP operations.

Axios has remained the most popular third-party choice throughout this period due to its superior error handling and request/response interceptors. However, Got has gained traction in the Node.js community for its performance and built-in retry logic. The trend shows developers moving toward native APIs when possible, but preferring specialized libraries for complex scenarios requiring middleware patterns.

Expert Tips

1. Always Use Request Timeouts

Set a timeout on every request. Without it, a slow or hung server can block your entire application. Use 10 seconds for external APIs, 5 seconds for internal services.

// Axios
const response = await apiClient.post(endpoint, data, {
  timeout: 10000, // 10 seconds
});

// Fetch
await fetch(url, {
  signal: AbortSignal.timeout(10000),
});

2. Implement Exponential Backoff for Retries

When a POST request fails due to network issues or temporary server problems (5xx errors), retry with increasing delays. This prevents overwhelming the server and gives it time to recover.

async function postWithRetry(
  url: string,
  data: RequestPayload,
  maxRetries: number = 3
): Promise<ApiResponse> {
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
    try {
      return await sendPostWithAxios(url, data);
    } catch (error) {
      if (attempt === maxRetries) throw error;
      const delay = Math.pow(2, attempt) * 1000; // 1s, 2s, 4s
      await new Promise((resolve) => setTimeout(resolve, delay));
    }
  }
  throw new Error('Max retries exceeded');
}

3. Validate Response Data with Zod or Similar

Don’t assume the server returns what you expect. Use a schema validation library to parse and validate responses at runtime, catching API contract changes early.

import { z } from 'zod';

const ApiResponseSchema = z.object({
  id: z.number(),
  status: z.string(),
  createdAt: z.string().datetime(),
});

const response = await sendPostWithAxios('/endpoint', data);
const validatedData = ApiResponseSchema.parse(response);

4. Use Environment Variables for Endpoints and Credentials

Never hardcode API URLs or authentication tokens. Store them in environment variables or a secure vault. This makes your code portable across development, staging, and production.

const apiUrl = process.env.API_BASE_URL || 'https://api.example.com';
const apiToken = process.env.API_TOKEN;

if (!apiToken) {
  throw new Error('API_TOKEN environment variable is required');
}

5. Log Requests and Responses for Debugging

Add logging middleware to capture request/response data, especially in production. This saves hours when debugging API integration issues. Use Axios interceptors or custom wrappers.

apiClient.interceptors.request.use((config) => {
  console.log(`POST ${config.url} with payload:`, config.data);
  return config;
});

apiClient.interceptors.response.use(
  (response) => {
    console.log(`Response: ${response.status}`, response.data);
    return response;
  },
  (error) => {
    console.error(`Error: ${error.message}`);
    return Promise.reject(error);
  }
);

People Also Ask

Is this the best way to how to send POST request in TypeScript?

For the most accurate and current answer, see the detailed data and analysis in the sections above. Our data is updated regularly with verified sources.

What are common mistakes when learning how to send POST request in TypeScript?

For the most accurate and current answer, see the detailed data and analysis in the sections above. Our data is updated regularly with verified sources.

What should I learn after how to send POST request in TypeScript?

For the most accurate and current answer, see the detailed data and analysis in the sections above. Our data is updated regularly with verified sources.

FAQ Section

Q1: Do I need to install anything to send POST requests in TypeScript?

A: It depends on your environment. If you’re using Node.js 18+ or running in a modern browser, the built-in fetch API is available without installation. However, many production projects prefer axios (install with npm install axios) because it automatically handles JSON serialization, rejects on error status codes, and provides better error messages. For the native Node.js HTTP module, no installation is needed—it’s built-in—but it’s more verbose and lower-level.

Q2: Why does my fetch POST request return status 200 but my code is in the catch block?

A: This is a common gotcha with fetch. The fetch API only rejects the promise on network errors (DNS failures, timeouts, etc.)—it does not reject on HTTP error statuses like 404 or 500. You must manually check the response.ok property or response.status code. Axios handles this differently: it automatically rejects for any status outside the 2xx range. Always check if (!response.ok) when using fetch.

Q3: How do I send authentication with my POST request?

A: Add the authorization header to your request. For Bearer tokens, use Authorization: Bearer YOUR_TOKEN. For API keys, the header varies by API (often X-API-Key or Authorization: ApiKey YOUR_KEY). Never hardcode tokens in source code—always read them from environment variables or a secrets manager. Example: headers: { 'Authorization': `Bearer ${process.env.API_TOKEN}` }.

Q4: What’s the difference between ‘application/json’ and ‘application/x-www-form-urlencoded’ content types?

A: application/json sends structured data as JSON (e.g., {"name": "John"}), which is standard for REST APIs. application/x-www-form-urlencoded sends data as URL-encoded key-value pairs (e.g., name=John&email=john@example.com), which is used for HTML form submissions. APIs expect specific content types—using the wrong one causes 400 Bad Request errors. Most modern APIs use JSON.

Q5: How do I handle different error scenarios when sending a POST request?

A: Network errors, server errors (5xx), and client errors (4xx) need different handling. Use try-catch with type checking to distinguish them. For fetch, check response.status. For Axios, catch AxiosError and inspect error.response.status. Network timeouts and connection failures appear as TypeErrors in fetch or AxiosError without a response. Log the full error message and status code—this information is crucial for debugging. Implement retry logic with exponential backoff for transient failures (network issues, 503 Service Unavailable).

Conclusion

Sending POST requests in TypeScript is straightforward once you understand the three core approaches: fetch for simplicity and modern environments, Axios for production apps with complex requirements, and Node.js HTTP for minimal dependencies. The key is handling errors properly, validating response data, and using TypeScript’s type system to prevent runtime surprises.

Start with fetch if you’re new to HTTP requests—it’s built-in and teaches you fundamental concepts. Move to Axios once your project needs request interceptors, automatic retries, or cleaner error handling. Always set timeouts, validate responses, store credentials in environment variables, and add logging for debugging.

The most common mistakes are ignoring error cases, forgetting to check response status codes, and hardcoding API credentials. Avoid these, and your POST request code will be robust, maintainable, and production-ready.

Similar Posts