← Back to Blog
Tutorial5 min read

How to Integrate Face Recognition in Node.js (Express Example)

Step-by-step tutorial: add face registration, recognition, and liveness detection to a Node.js app with the ARSA Face Recognition API and native fetch.

What We're Building

In this tutorial you'll add three face recognition capabilities to a Node.js backend:

  • Register a face with a unique ID
  • Recognize a face against your registered database (1:N search)
  • Check liveness to make sure the submitted image is a real person, not a photo of a photo
  • We'll use the ARSA Face Recognition API — a REST API, so there is no ML runtime to install, no model files to download, and no GPU required. Everything runs over HTTPS with multipart/form-data uploads. If you prefer Python, see our Python integration guide; for browser-side capture, see real-time face detection in JavaScript.

    Prerequisites

  • • Node.js 18+ (we use the built-in fetch and FormData — no axios needed)
  • • An ARSA API key — register free and copy the key from your dashboard (100 calls/month on the free tier)
  • Store the key in an environment variable, never in code:

    bash
    export ARSA_API_KEY="your_api_key_here"
    

    A Minimal API Client

    Every ARSA endpoint authenticates with the x-key-secret header. Here's a small client module:

    javascript
    // arsaClient.js
    const BASE_URL = "https://faceapi.arsa.technology/api/v1";
    const API_KEY = process.env.ARSA_API_KEY;
    
    async function arsaFetch(path, { method = "POST", headers = {}, body } = {}) {
      const res = await fetch(`${BASE_URL}${path}`, {
        method,
        headers: { "x-key-secret": API_KEY, ...headers },
        body,
      });
      const data = await res.json();
      if (!res.ok) throw new Error(data.message || `HTTP ${res.status}`);
      return data;
    }
    
    module.exports = { arsaFetch };
    

    Step 1: Register a Face

    Registration converts one or more photos of a person into a face embedding stored in your isolated database, keyed by a face_uid you choose (an employee ID, user UUID, etc. — alphanumeric, hyphens, underscores, dots, max 255 chars).

    javascript
    // register.js
    const fs = require("fs");
    const { arsaFetch } = require("./arsaClient");
    
    async function registerFace(faceUid, imagePath) {
      const form = new FormData();
      const buffer = fs.readFileSync(imagePath);
      form.append("face_image", new Blob([buffer], { type: "image/jpeg" }), "face.jpg");
    
      return arsaFetch("/face_recognition/register_face", {
        headers: { "x-face-uid": faceUid },
        body: form,
      });
    }
    
    registerFace("employee-001", "./alice.jpg").then(console.log);
    // { status: 'success', message: 'employee-001 registered successfully', latency_ms: ... }
    

    Tips for good registrations:

  • • Use a clear, front-facing photo — one face per image
  • • You can attach up to three extra photos (additional_face_image_0..2) of the same person to improve matching across angles and lighting
  • • Images must be JPEG or PNG, max 10 MB
  • Step 2: Recognize a Face (1:N Search)

    Recognition takes a new photo and searches your whole registered database for the best match — this is 1:N face recognition, the operation behind attendance systems and face login.

    javascript
    // recognize.js
    const fs = require("fs");
    const { arsaFetch } = require("./arsaClient");
    
    async function recognizeFace(imagePath) {
      const form = new FormData();
      const buffer = fs.readFileSync(imagePath);
      form.append("face_image", new Blob([buffer], { type: "image/jpeg" }), "query.jpg");
    
      return arsaFetch("/face_recognition/recognize_face", { body: form });
    }
    
    recognizeFace("./checkin-photo.jpg").then(result => {
      for (const face of result.faces) {
        console.log(face);
      }
    });
    

    The response includes the matched face_uid and a similarity score for each detected face. How you threshold that score matters — read our guide to similarity thresholds and match scores before shipping.

    Step 3: Add Liveness Detection

    Recognition alone can be fooled by a printed photo or a phone screen. Passive liveness detection analyzes a single image for spoof artifacts — no user interaction required:

    javascript
    // liveness.js
    const fs = require("fs");
    const { arsaFetch } = require("./arsaClient");
    
    async function checkLiveness(imagePath) {
      const form = new FormData();
      const buffer = fs.readFileSync(imagePath);
      form.append("face_image", new Blob([buffer], { type: "image/jpeg" }), "selfie.jpg");
    
      return arsaFetch("/face_liveness", { body: form });
    }
    

    For high-security flows such as KYC onboarding, chain the calls: liveness first, then recognition or 1:1 validation only if the image is live.

    Wiring It Into Express

    Here's the pattern for an Express route that accepts an upload from your frontend and proxies it to ARSA — your API key stays on the server, which is rule #1 of API key security:

    javascript
    // server.js
    const express = require("express");
    const multer = require("multer");
    const { arsaFetch } = require("./arsaClient");
    
    const app = express();
    const upload = multer({ storage: multer.memoryStorage(), limits: { fileSize: 10 * 1024 * 1024 } });
    
    app.post("/api/checkin", upload.single("photo"), async (req, res) => {
      try {
        const form = new FormData();
        form.append("face_image", new Blob([req.file.buffer], { type: req.file.mimetype }), "photo.jpg");
    
        const liveness = await arsaFetch("/face_liveness", { body: form });
        if (liveness.faces?.some(f => f.liveness === "fake")) {
          return res.status(403).json({ error: "Spoof detected" });
        }
    
        const form2 = new FormData();
        form2.append("face_image", new Blob([req.file.buffer], { type: req.file.mimetype }), "photo.jpg");
        const match = await arsaFetch("/face_recognition/recognize_face", { body: form2 });
        res.json(match);
      } catch (err) {
        res.status(502).json({ error: err.message });
      }
    });
    
    app.listen(3000);
    

    Handling Errors and Limits

  • 429 responses mean you've hit your plan's rate or monthly quota — catch them and back off
  • 400 with "No face detected" — ask the user for a clearer photo
  • • Log the returned latency_ms to monitor performance from your region
  • Next Steps

  • • Explore face analytics to get age, gender, and expression from the same image
  • • Add active liveness (head-pose challenge video) for your highest-risk flows
  • • Compare providers in our face recognition API buyer's guide
  • The whole integration above fits in under 100 lines of Node.js. Grab a free API key and try it against your own photos.

    Ready to get started?

    Try ARSA Face Recognition API free with 100 API calls/month.

    Start Free Trial