CS651 | Web Systems
  • outline
  • projects
  • syllabus
  • links

 

Basics behind NodeJS+Express + Google Cloud Vision

 

 

 

It’s a tiny image-labeling web app. In the browser, a user selects an image and clicks Upload. Your Express server receives that image (via Multer), sends the raw bytes to Google Cloud Vision’s Label Detection API, gets back a list of labels (e.g., “Cat”, “Mammal”, “Pet”), and returns those labels as JSON. The page then renders the labels as a list. There are 2 primary files index.js & index.html described below. Depending on your IDE setup your project file structure (& IDE of choice) ****MAY*** look something like the following:

 

Project structure showing index.js and index.html in NodeJS Express project

 

PRE-REQ: install in NodeJS+Express Project appropriate modules

 

 

npm install @google-cloud/vision multer

 

@google-cloud/vision

  • Purpose: The official Google Cloud Vision API client library for Node.js.

  • What it does:
    It lets your code easily call Google Cloud Vision services (like label detection, OCR, logo detection, etc.) without manually sending REST API requests.

 

multer

  • Purpose: Middleware for handling multipart/form-data, primarily used for file uploads in Express.

  • What it does:
    It parses incoming HTTP POST requests that contain file uploads (e.g., form submissions from HTML <input type="file">).

 

 

UNDERSTANDING/PRE-REQ: Google Cloud Project (GAE) deploymet special setup


What “credentials” are needed by your GAE webapp

When your app calls the Google Cloud Vision API, Google’s servers must know who you are and that you have permission to use the Vision API.
That proof of identity and permission is provided through credentials.

 

In Node.js, the Vision client library (@google-cloud/vision) automatically looks for those credentials in a few standard ways.


How the Vision client finds credentials

When you create this client:

const client = new ImageAnnotatorClient();  

you aren’t explicitly passing any credentials. Instead, the library automatically searches for them in this order (simplified):

  1. Environment variable
    If you’ve set an environment variable named GOOGLE_APPLICATION_CREDENTIALS, it should point to a JSON key file that looks like this:

    export GOOGLE_APPLICATION_CREDENTIALS="/path/to/service-account-key.json"  

    That JSON file contains a service account key—a secure identity representing your project or an App Engine service account—with permission to access the Vision API.

    When you run your app locally, this is the most common setup.

  2. App Engine default credentials (EASIEST) --will only work if deployed to GAE (can not run locally)
    When you deploy to Google App Engine (GAE), you don’t need to set or upload that key file manually.
    GAE automatically provides a built-in service account for your application. The Vision client automatically detects and uses it.

    This account usually has an email like:

    <project-id>@appspot.gserviceaccount.com  

    and it comes with permissions that allow your app to call other Google Cloud APIs (like Vision), assuming you’ve enabled them in your project.


Example: running locally vs. deployed

Environment How it authenticates What you need to do
Local development Uses the key file you specify via GOOGLE_APPLICATION_CREDENTIALS Create a service account in Google Cloud Console, download its JSON key, and set the environment variable before running the app (SEE BELOW)
Google App Engine Uses the App Engine default service account automatically No key file needed; just ensure the Vision API is enabled and the account has permission

Permissions you need

The service account (whether it’s your own or the default App Engine one) must have a role that includes Vision access.
For example:

  • roles/editor (broad, includes Vision)

  • or more narrowly, roles/vision.user

Without that, the Vision client will return an authorization error.


Summary

So we can say

“This client uses your Google Cloud credentials (through GOOGLE_APPLICATION_CREDENTIALS or the App Engine default credentials) to call the Vision API.”

It means:

  • Your app never manually includes passwords or tokens in code.

  • The Vision library automatically finds the right secure credentials depending on where it’s running.

  • Those credentials allow the Vision service to recognize your app, check its permissions, and bill your project for API usage.

 

 

 

 

CODE:

index.js

 

Explanation (see steps in code below)

STEP 1:
Import the required modules — Express (for building the web server), Google Cloud Vision (for performing image analysis), and Multer (for handling image uploads).

STEP 2:
Initialize the Express application, create a Vision API client, and configure Multer to store uploaded images in memory rather than on disk.

STEP 3:
Tell Express to serve static files from the public directory so that users can access the HTML, CSS, and JavaScript files through their browsers.

STEP 4:
Enable JSON body parsing in Express to handle data sent in JSON format from client requests.

STEP 5:
Define a POST route (for example, /analyze) that uses Multer middleware to receive and process an uploaded image file from the client’s form submission.

STEP 6:
This step is triggered by the frontend script inside your webpage.
When a user selects an image and clicks “Analyze,” the script in the browser sends the image file to this backend route using an HTTP POST request.
Here, the uploaded image (req.file.buffer) is passed to the Google Cloud Vision API for analysis (such as label or text detection).
Once the Vision API returns its results, the backend sends the analysis data back to the browser as a JSON response.
The frontend script then displays these results dynamically to the user on the webpage.

STEP 7:
Add a centralized error-handling function that captures any runtime or Vision API errors and returns a clean, structured error message to the client.

STEP 8:
Start the Express server and listen on the specified port so that the application can accept and respond to incoming requests from the frontend.

 

 

const express = require('express');

const { ImageAnnotatorClient } =  require('@google-cloud/vision');

const multer = require('multer');
const path = require('path');
const app = express();
const port = 3000; 
                  
//STEP 1:  Configure Multer for file uploads
//Uses in-memory storage so you don’t write to disk; Vision can read the buffer directly.
//Enforces a 5 MB limit to avoid very large uploads. 
const upload = multer({ storage: multer.memoryStorage(), // Store image in memory as a buffer limits: { fileSize: 5 * 1024 * 1024 }, // 5MB limit }); // STEP 2: Initialize Google Cloud Vision client // Ensure your GOOGLE_APPLICATION_CREDENTIALS environment vaable // is set to the path of your service account key file, or provide // credentials directly. /**
* you aren’t explicitly passing any credentials. Instead, the library automatically * searches for them in this order (simplified): * 1) Enironment variable If you’ve set an environment variable named GOOGLE_APPLICATION_CREDENTIALS, it should point to a JSON key file * that looks like this: * export GOOGLE_APPLICATION_CREDENTIALS="/path/to/service-account-key.json" * NOTE: That JSON file contains a service account key—a secure identity representing your project or an App Engine service
* account—with permission to access the Vision API. * * 2)EASIEST OPTION: App Engine default credentials
* When you deploy to Google App Engine (GAE), you don’t need to set or upload that key file manually.
* GAE automatically provides a built-in service account for your application. The Vision client automatically * detects and uses it. * * **/ const client = new ImageAnnotatorClient();
//STEP 3: Serve static files from 'public' directory app.use(express.static('public')); //STEP 4: Serve a basic HTML form for image upload app.get('/', (req, res) => { res.sendFile(path.join(__dirname, 'public', 'index.html')); }); //STEP 5: Handle image upload and Vision API call /** *>upload.single('image') tells Multer to look for a single file in the form field named "image", then puts it on req.file as a Buffer. * Calls Vision.labelDetection with the raw bytes. * Extracts the human-readable description from each returned label and replies with { labels: [...] }. * * On failure, logs the error and returns HTTP 500 with a plain text message. **/ app.post('/upload', upload.single('image'), async (req, res) => { if (!req.file) { return res.status(400).send('No image uploaded.'); } try { //request Visiona API to do LabelelDetection const [result] = await client.labelDetection({ image: { content: req.file.buffer }, }); //recieve resuts & return as JSON object of labels. const labels = result.labelAnnotations.map(label => label.description); res.json({ labels }); } catch (error) { console.error('Error processing image with Vision API:', error); res.status(500).send('Error processing image.'); } }); //STEP 6: launch listinging at port number app.listen(port, () => { console.log(`Server listening at http://localhost:${port}`); });

 

 

 

index.html

 

explanation of html (can make part of ejs instead)

  1. A standard multipart form with a file input named "image" (this must match upload.single('image') on the server).
  2. An empty <div> to display the results.
  3. SCRIPT - Prevents the default submit, builds a FormData object (which includes the chosen file), and posts it to /upload.
  • Expects a JSON response with a labels array.

  • If present, renders the labels as an unordered list; otherwise shows a generic error.

     


use-case Flow: request–response flow at runtime

  1. User navigates to / → server sends index.html.

  2. User picks an image and clicks Upload → JS intercepts the submit, posts a FormData with field image to /upload.

  3. Express + Multer parse the multipart body; req.file.buffer holds the image bytes (max 5 MB).

  4. Vision API (client.labelDetection) analyzes the image and returns label annotations (each with description, score, etc.).

  5. Server responds with { "labels": ["Cat", "Mammal", "Whiskers", ...] }.

  6. Browser renders the list of labels inside #results.

 

<!DOCTYPE html>
<html lang="en">
<head>
<meta  charset="UTF-8">
<meta  name="viewport" content="width=device-width,  initial-scale=1.0">
<title>Google Cloud Vision API Demo</title>
</head>
<body>

<h1>Upload  an Image for Label Detection</h1>
<form  action="/upload" method="POST"  enctype="multipart/form-data">
<input  type="file" name="image" accept="image/*"  required>
<button  type="submit">Upload Image</button>
</form>
<div  id="results"></div>
<script>
 document.querySelector('form').addEventListener('submit', async (event)  => {
     event.preventDefault();
     const  formData = new FormData(event.target);
     const  response = await fetch('/upload', {
               method: 'POST',
               body:  formData
             });
const data = await response.json(); const resultsDiv = document.getElementById('results'); if (data.labels) { resultsDiv.innerHTML = `<h2>Detected Labels:</h2><ul>${data.labels.map(label => `<li>${label}</li>`).join('')}</ul>`; } else { resultsDiv.innerHTML = `<p>Error: ${data}</p>`; } }); /script> </body> </html>

 

 

 


 

(OPTIONAL) Setup Credentials json file for running locally

 

step by step so you can create a service account key file for local testing of your Vision API app.


Overview

You’re going to:

  1. Create a service account (a special Google identity for apps, not people).

  2. Give it permission to use the Vision API.

  3. Download its JSON key file.

  4. Tell your app where to find that key file using the GOOGLE_APPLICATION_CREDENTIALS environment variable.

This lets your local Node.js app authenticate exactly like it will on App Engine.


Step-by-Step Guide

1. Open the Google Cloud Console

Go to:
https://console.cloud.google.com/iam-admin/serviceaccounts

Make sure you’ve selected the same project where your Vision API is enabled.

 

2. Click “Create Service Account”

Give it:

  • Name: vision-demo (or any name you like)

  • ID: auto-generated

  • Description: “Service account for Vision API testing”

Click Create and continue.

 

3. Assign a role

Under Grant this service account access to project, choose:

  • Role: Vision AI > Vision AI User
    (or if you prefer broader permissions, choose “Editor”)

Click Continue then Done.

 

4. Generate a key

In the list of service accounts:

  1. Find your new one (vision-demo@<project>.iam.gserviceaccount.com).

  2. Click the three dots on the right → Manage keys.

  3. Click Add key → Create new key.

  4. Choose JSON → Create.

A .json file will be downloaded to your computer — this is your key file.

It looks something like this (simplified example):

{    "type": "service_account",  
     "project_id": "my-vision-demo",  
     "private_key_id": "b1a2c3d4e5f678901234567890abcdef12345678",    
     "private_key": "-----BEGIN PRIVATE KEY-----\nMIIEv...<snip>...==\n-----END PRIVATE KEY-----\n",
	   "client_email": "vision-demo@my-vision-demo.iam.gserviceaccount.com",
	   "client_id": "123456789012345678901",
	   "auth_uri": "https://accounts.google.com/o/oauth2/auth",
	   "token_uri": "https://oauth2.googleapis.com/token",   
     "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
	   "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/vision-demo%40my-vision-demo.iam.gserviceaccount.com"
}  

IMPORRANT: Keep this file private — it gives full access to your GCP project for whatever role you assigned.


 

 

5. Set the environment variable

In your terminal (macOS/Linux):

export GOOGLE_APPLICATION_CREDENTIALS="/path/to/your/vision-demo-key.json"  

Or on Windows PowerShell:

setx GOOGLE_APPLICATION_CREDENTIALS "C:\path\to\your\vision-demo-key.json"  

To confirm:

echo $GOOGLE_APPLICATION_CREDENTIALS  

 

6. Test locally (from command line ---or you do it from your IDE)

Now in the same terminal:

npm install  node index.js  

Open http://localhost:3000, upload an image, and you should see detected labels.

Behind the scenes:

  • The Vision client library reads the path from the GOOGLE_APPLICATION_CREDENTIALS variable.

  • It opens the JSON file, uses the private key to authenticate, and gets a short-lived access token from Google Cloud.

  • That token authorizes calls to the Vision API.

     

7. When you deploy to App Engine

You can delete or ignore the environment variable — App Engine automatically uses its default service account, and your code will still work without modification.

 

 

cs651:web systems

  • home
  • outline
  • projects
  • syllabus
  • links