Next.js icon

Next.js Quick Start Guide

Get started with FacePing's face recognition API using Next.js

Prerequisites

Installation

First, create a new Next.js project with TypeScript:

npx create-next-app@latest faceping-demo --typescript
cd faceping-demo
npm install

Environment Setup

Create a .env.local file in your project root:

NEXT_PUBLIC_FACEPING_API_URL=https://api.faceping.ai
FACEPING_API_KEY=your_api_key_here

Step 1: Create API Utilities

Create a utility file for API calls (utils/faceping.ts):

// utils/faceping.ts
export const FACEPING_API_URL = process.env.NEXT_PUBLIC_FACEPING_API_URL;

export async function createGroup(groupName: string) {
  try {
    const response = await fetch(`${FACEPING_API_URL}/groups/${groupName}`, {
      method: 'POST'
    });
    
    if (!response.ok) {
      throw new Error('Failed to create group');
    }
    
    return await response.json();
  } catch (error) {
    console.error('Error creating group:', error);
    throw error;
  }
}

export async function uploadFace(groupName: string, file: File) {
  try {
    const formData = new FormData();
    formData.append('image', file);
    
    const response = await fetch(
      `${FACEPING_API_URL}/groups/${groupName}/faces`,
      {
        method: 'POST',
        body: formData
      }
    );
    
    if (!response.ok) {
      throw new Error('Failed to upload face');
    }
    
    return await response.json();
  } catch (error) {
    console.error('Error uploading face:', error);
    throw error;
  }
}

export async function searchFaces(groupName: string, file: File) {
  try {
    const formData = new FormData();
    formData.append('image', file);
    
    const response = await fetch(
      `${FACEPING_API_URL}/groups/${groupName}/search`,
      {
        method: 'POST',
        body: formData
      }
    );
    
    if (!response.ok) {
      throw new Error('Failed to search faces');
    }
    
    return await response.json();
  } catch (error) {
    console.error('Error searching faces:', error);
    throw error;
  }
}

Step 2: Create Components

Create a component for face group management (components/FaceGroup.tsx):

// components/FaceGroup.tsx
'use client';

import { useState } from 'react';
import { createGroup } from '../utils/faceping';

export default function FaceGroup() {
  const [groupName, setGroupName] = useState('');
  const [status, setStatus] = useState('');

  const handleCreateGroup = async () => {
    try {
      await createGroup(groupName);
      setStatus('Group created successfully');
      setGroupName('');
    } catch (error) {
      setStatus('Error creating group');
    }
  };

  return (
    <div className="space-y-4">
      <input
        type="text"
        value={groupName}
        onChange={(e) => setGroupName(e.target.value)}
        placeholder="Enter group name"
        className="w-full p-2 border rounded"
      />
      <button
        onClick={handleCreateGroup}
        className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"
      >
        Create Group
      </button>
      {status && <p className="mt-2 text-sm">{status}</p>}
    </div>
  );
}

Create a component for face upload (components/FaceUpload.tsx):

// components/FaceUpload.tsx
'use client';

import { useState, useRef } from 'react';
import { uploadFace } from '../utils/faceping';

export default function FaceUpload() {
  const [groupName, setGroupName] = useState('');
  const [status, setStatus] = useState('');
  const fileRef = useRef<HTMLInputElement>(null);

  const handleUpload = async () => {
    const file = fileRef.current?.files?.[0];
    if (!file) return;

    try {
      await uploadFace(groupName, file);
      setStatus('Face uploaded successfully');
      setGroupName('');
      if (fileRef.current) fileRef.current.value = '';
    } catch (error) {
      setStatus('Error uploading face');
    }
  };

  return (
    <div className="space-y-4">
      <input
        type="text"
        value={groupName}
        onChange={(e) => setGroupName(e.target.value)}
        placeholder="Enter group name"
        className="w-full p-2 border rounded"
      />
      <input
        type="file"
        ref={fileRef}
        accept="image/*"
        className="w-full"
      />
      <button
        onClick={handleUpload}
        className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"
      >
        Upload Face
      </button>
      {status && <p className="mt-2 text-sm">{status}</p>}
    </div>
  );
}

Create a component for face search (components/FaceSearch.tsx):

// components/FaceSearch.tsx
'use client';

import { useState, useRef } from 'react';
import { searchFaces } from '../utils/faceping';

interface MatchResult {
  score: number;
}

export default function FaceSearch() {
  const [groupName, setGroupName] = useState('');
  const [status, setStatus] = useState('');
  const [results, setResults] = useState<MatchResult[]>([]);
  const fileRef = useRef<HTMLInputElement>(null);

  const handleSearch = async () => {
    const file = fileRef.current?.files?.[0];
    if (!file) return;

    try {
      const data = await searchFaces(groupName, file);
      setResults(data.matches || []);
      setStatus('Search completed');
    } catch (error) {
      setStatus('Error searching faces');
      setResults([]);
    }
  };

  return (
    <div className="space-y-4">
      <input
        type="text"
        value={groupName}
        onChange={(e) => setGroupName(e.target.value)}
        placeholder="Enter group name"
        className="w-full p-2 border rounded"
      />
      <input
        type="file"
        ref={fileRef}
        accept="image/*"
        className="w-full"
      />
      <button
        onClick={handleSearch}
        className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"
      >
        Search Faces
      </button>
      {status && <p className="mt-2 text-sm">{status}</p>}
      {results.length > 0 && (
        <div className="mt-4 space-y-2">
          {results.map((match, index) => (
            <div key={index} className="p-2 bg-gray-50 rounded">
              Match Score: {match.score}
            </div>
          ))}
        </div>
      )}
    </div>
  );
}

Step 3: Create Main Page

Update your main page (app/page.tsx):

// app/page.tsx
export default function Home() {
  return (
    <main className="container mx-auto px-4 py-8">
      <h1 className="text-3xl font-bold mb-8">FacePing Demo</h1>
      
      <div className="space-y-8">
        <section>
          <h2 className="text-xl font-semibold mb-4">Create Face Group</h2>
          <FaceGroup />
        </section>

        <section>
          <h2 className="text-xl font-semibold mb-4">Upload Face</h2>
          <FaceUpload />
        </section>

        <section>
          <h2 className="text-xl font-semibold mb-4">Search Faces</h2>
          <FaceSearch />
        </section>
      </div>
    </main>
  );
}

Step 4: API Error Handling

Create a custom error handling utility (utils/error.ts):

// utils/error.ts
export class APIError extends Error {
  constructor(
    message: string,
    public status?: number,
    public code?: string
  ) {
    super(message);
    this.name = 'APIError';
  }
}

export function handleAPIError(error: unknown): APIError {
  if (error instanceof APIError) {
    return error;
  }
  
  if (error instanceof Error) {
    return new APIError(error.message);
  }
  
  return new APIError('An unknown error occurred');
}

Step 5: Add Loading States

Create a loading component (components/LoadingSpinner.tsx):

// components/LoadingSpinner.tsx
export default function LoadingSpinner() {
  return (
    <div className="flex justify-center">
      <div className="animate-spin rounded-full h-6 w-6 border-b-2 border-blue-500"></div>
    </div>
  );
}

Update the FaceSearch component to include loading state:

// components/FaceSearch.tsx
'use client';

import { useState, useRef } from 'react';
import { searchFaces } from '../utils/faceping';
import LoadingSpinner from './LoadingSpinner';

export default function FaceSearch() {
  const [groupName, setGroupName] = useState('');
  const [status, setStatus] = useState('');
  const [results, setResults] = useState<MatchResult[]>([]);
  const [isLoading, setIsLoading] = useState(false);
  const fileRef = useRef<HTMLInputElement>(null);

  const handleSearch = async () => {
    const file = fileRef.current?.files?.[0];
    if (!file) return;

    setIsLoading(true);
    try {
      const data = await searchFaces(groupName, file);
      setResults(data.matches || []);
      setStatus('Search completed');
    } catch (error) {
      setStatus('Error searching faces');
      setResults([]);
    } finally {
      setIsLoading(false);
    }
  };

  return (
    <div className="space-y-4">
      {/* ... existing inputs ... */}
      
      <button
        onClick={handleSearch}
        disabled={isLoading}
        className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600 disabled:opacity-50"
      >
        {isLoading ? <LoadingSpinner /> : 'Search Faces'}
      </button>
      
      {/* ... existing results display ... */}
    </div>
  );
}

Environment Variables

Make sure to set up your environment variables properly. Create a .env.local file in your project root:

NEXT_PUBLIC_FACEPING_API_URL=https://api.faceping.ai
FACEPING_API_KEY=your_api_key_here

# Add this to your .gitignore
.env*.local

Security Notes

  • Images are immediately discarded after vector conversion
  • Face vectors cannot be reverse-engineered into images
  • Always use HTTPS for all API calls
  • Keep your API keys in environment variables

Ready to get started?

Take a look at the API documentation
API docs