Chameleon

API Calls

Learn how to make API calls in Chameleon

API Calls

Learn how to create API routes and make API calls in Chameleon using Next.js App Router.

Overview

Chameleon uses Next.js App Router for API routes and provides both server-side and client-side API calling capabilities. API routes are created in the src/app/api/ directory and can be called from components or other API routes.

API Routes

Basic API Route

// src/app/api/hello/route.ts
import { NextRequest, NextResponse } from "next/server";

export async function GET(request: NextRequest) {
  return NextResponse.json({ message: "Hello, World!" });
}

export async function POST(request: NextRequest) {
  const body = await request.json();
  return NextResponse.json({ received: body });
}

API Route with Authentication

// src/app/api/user/profile/route.ts
import { NextRequest, NextResponse } from "next/server";
import { auth } from "@/auth";

export async function GET(request: NextRequest) {
  const session = await auth();
  
  if (!session) {
    return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
  }
  
  return NextResponse.json({
    user: {
      id: session.user.uuid,
      email: session.user.email,
      name: session.user.nickname,
    },
  });
}

API Route with Database

// src/app/api/posts/route.ts
import { NextRequest, NextResponse } from "next/server";
import { getPosts, createPost } from "@/models/post";

export async function GET(request: NextRequest) {
  try {
    const { searchParams } = new URL(request.url);
    const page = parseInt(searchParams.get("page") || "1");
    const limit = parseInt(searchParams.get("limit") || "10");
    
    const posts = await getPosts({ page, limit });
    
    return NextResponse.json({
      posts,
      pagination: {
        page,
        limit,
        total: posts.length,
      },
    });
  } catch (error) {
    return NextResponse.json(
      { error: "Failed to fetch posts" },
      { status: 500 }
    );
  }
}

export async function POST(request: NextRequest) {
  try {
    const session = await auth();
    if (!session) {
      return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
    }
    
    const body = await request.json();
    const post = await createPost({
      ...body,
      author_id: session.user.uuid,
    });
    
    return NextResponse.json(post, { status: 201 });
  } catch (error) {
    return NextResponse.json(
      { error: "Failed to create post" },
      { status: 500 }
    );
  }
}

Client-Side API Calls

Using Fetch

// src/components/api-example.tsx
"use client";

import { useState, useEffect } from "react";

export function ApiExample() {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    const fetchData = async () => {
      try {
        const response = await fetch("/api/hello");
        if (!response.ok) {
          throw new Error("Failed to fetch data");
        }
        const result = await response.json();
        setData(result);
      } catch (err) {
        setError(err.message);
      } finally {
        setLoading(false);
      }
    };

    fetchData();
  }, []);

  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error: {error}</div>;

  return <div>{JSON.stringify(data)}</div>;
}

API Call with Authentication

// src/components/user-profile.tsx
"use client";

import { useState } from "react";
import { useSession } from "next-auth/react";

export function UserProfile() {
  const { data: session } = useSession();
  const [profile, setProfile] = useState(null);

  const fetchProfile = async () => {
    try {
      const response = await fetch("/api/user/profile", {
        headers: {
          "Content-Type": "application/json",
        },
      });
      
      if (!response.ok) {
        throw new Error("Failed to fetch profile");
      }
      
      const data = await response.json();
      setProfile(data.user);
    } catch (error) {
      console.error("Error fetching profile:", error);
    }
  };

  if (!session) {
    return <div>Please sign in to view your profile.</div>;
  }

  return (
    <div>
      <button onClick={fetchProfile}>Load Profile</button>
      {profile && (
        <div>
          <h2>{profile.name}</h2>
          <p>{profile.email}</p>
        </div>
      )}
    </div>
  );
}

Form Submission with API

// src/components/contact-form.tsx
"use client";

import { useState } from "react";

export function ContactForm() {
  const [isSubmitting, setIsSubmitting] = useState(false);
  const [message, setMessage] = useState("");

  const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    setIsSubmitting(true);
    setMessage("");

    try {
      const formData = new FormData(e.currentTarget);
      const data = {
        name: formData.get("name"),
        email: formData.get("email"),
        message: formData.get("message"),
      };

      const response = await fetch("/api/contact", {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
        },
        body: JSON.stringify(data),
      });

      if (!response.ok) {
        throw new Error("Failed to send message");
      }

      setMessage("Message sent successfully!");
      e.currentTarget.reset();
    } catch (error) {
      setMessage("Error sending message. Please try again.");
    } finally {
      setIsSubmitting(false);
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <div>
        <input
          name="name"
          type="text"
          placeholder="Your Name"
          required
        />
      </div>
      <div>
        <input
          name="email"
          type="email"
          placeholder="Your Email"
          required
        />
      </div>
      <div>
        <textarea
          name="message"
          placeholder="Your Message"
          required
        />
      </div>
      <button type="submit" disabled={isSubmitting}>
        {isSubmitting ? "Sending..." : "Send Message"}
      </button>
      {message && <div>{message}</div>}
    </form>
  );
}

Error Handling

API Route Error Handling

// src/app/api/example/route.ts
import { NextRequest, NextResponse } from "next/server";

export async function GET(request: NextRequest) {
  try {
    // Your API logic here
    const result = await someAsyncOperation();
    
    return NextResponse.json({ success: true, data: result });
  } catch (error) {
    console.error("API Error:", error);
    
    return NextResponse.json(
      { 
        success: false, 
        error: "Internal server error",
        message: error instanceof Error ? error.message : "Unknown error"
      },
      { status: 500 }
    );
  }
}

Client-Side Error Handling

// src/lib/api-client.ts
export class ApiError extends Error {
  constructor(
    message: string,
    public status: number,
    public response?: Response
  ) {
    super(message);
    this.name = "ApiError";
  }
}

export async function apiCall<T>(
  url: string,
  options: RequestInit = {}
): Promise<T> {
  const response = await fetch(url, {
    headers: {
      "Content-Type": "application/json",
      ...options.headers,
    },
    ...options,
  });

  if (!response.ok) {
    const errorData = await response.json().catch(() => ({}));
    throw new ApiError(
      errorData.message || "API request failed",
      response.status,
      response
    );
  }

  return response.json();
}

// Usage
try {
  const data = await apiCall<UserProfile>("/api/user/profile");
  console.log(data);
} catch (error) {
  if (error instanceof ApiError) {
    console.error(`API Error ${error.status}: ${error.message}`);
  } else {
    console.error("Unexpected error:", error);
  }
}

File Locations

  • src/app/api/ - API route handlers
  • src/lib/api-client.ts - API client utilities
  • src/services/ - Business logic services
  • src/models/ - Data models and database operations

Common Tasks

Create a New API Route

  1. Create directory: src/app/api/your-endpoint/
  2. Add route.ts file
  3. Export HTTP method functions (GET, POST, PUT, DELETE)
  4. Add proper error handling
  5. Test the endpoint

Add Authentication to API Route

import { auth } from "@/auth";

export async function GET() {
  const session = await auth();
  if (!session) {
    return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
  }
  // Your protected logic here
}

Handle Form Data

export async function POST(request: NextRequest) {
  const formData = await request.formData();
  const file = formData.get("file") as File;
  const name = formData.get("name") as string;
  
  // Process form data
}

Add CORS Headers

export async function GET() {
  const response = NextResponse.json({ data: "example" });
  
  response.headers.set("Access-Control-Allow-Origin", "*");
  response.headers.set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE");
  response.headers.set("Access-Control-Allow-Headers", "Content-Type");
  
  return response;
}

Troubleshooting

API route not working

Problem: API endpoint returns 404

Solution:

  1. Check file is named route.ts
  2. Verify directory structure is correct
  3. Check for TypeScript errors
  4. Restart development server

CORS errors

Problem: Client can't call API from different domain

Solution:

  1. Add CORS headers to API response
  2. Configure middleware for CORS
  3. Use same domain for API calls
  4. Check browser network tab for errors

Authentication not working

Problem: API returns 401 even with valid session

Solution:

  1. Check session is properly retrieved
  2. Verify authentication middleware
  3. Check session cookie settings
  4. Test with different authentication methods

Next Steps