logo
Published on

Using Google Photos API in Next.js Blog

Using Google Photos API in Next.js Blog

This setup enables using personal Google Photos album as a banner image source for Next.js blog posts.

Google Cloud Project Configuration

  1. Create project in Google Cloud Console
  2. Set up OAuth credentials:
    • API & Services → Create Credentials
    • Select "Desktop app"
  3. Add test user:
    • API & Services → OAuth consent screen
    • Add your Google account under Test Users

Environment Settings

Token Generation

Use the script to get the refresh token for google apis. Document here: OAuth 2.0 for Native Applications

// scripts/get-google-refresh-token.js
require('dotenv').config({ path: '.env.local'})
const { google } = require('googleapis')
const express = require('express')
const app = express()

const client_id = process.env.GOOGLE_CLIENT_ID
const client_secret = process.env.GOOGLE_CLIENT_SECRET

const oauth2Client = new google.auth.OAuth2(
  client_id,
  client_secret,
  'http://localhost:3000/oauth2callback'
)

const authUrl = oauth2Client.generateAuthUrl({
  access_type: 'offline',
  scope: [
    'https://www.googleapis.com/auth/photoslibrary.readonly',
    'https://www.googleapis.com/auth/photoslibrary',
  ],
  prompt: 'consent',
})

app.get('/', (req, res) => {
  console.log('Redirecting to auth URL...')
  res.redirect(authUrl)
})

app.get('/oauth2callback', async (req, res) => {
  const { code } = req.query
  console.log('Received code:', code)

  try {
    const { tokens } = await oauth2Client.getToken(code)
    console.log('Received tokens:', tokens)
    res.send(`
      <h1>Your tokens:</h1>
      <p>Refresh Token: ${tokens.refresh_token}</p>
      <p>Access Token: ${tokens.access_token}</p>
    `)
  } catch (error) {
    console.error('Error getting tokens:', error)
    res.send('Error: ' + error.message)
  }
})

app.listen(3000, () => {
  console.log('Visit http://localhost:3000 to start the auth flow')
})

Run script:

npm install googleapis express
node scripts/get-google-refresh-token.js

Environment Variables Setup

  • Create the albums for banner images, the Album ID will be returned
  • Upload banner images to this album
curl -X POST 'https://photoslibrary.googleapis.com/v1/albums' \
-H 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
-H 'Content-Type: application/json' \
-d '{
  "album": {
    "title": "Banners"
  }
}'

Environment Configuration -> Create .env.local:

GOOGLE_CLIENT_ID=your_client_id
GOOGLE_CLIENT_SECRET=your_client_secret
GOOGLE_REFRESH_TOKEN=your_refresh_token
GOOGLE_PHOTOS_ALBUM_ID=your_album_id

Implementation

The implementation of calling APIs in a single flow:

  • Token Refreshing: Get the access token by using refresh token
  • Photo Fetching: Use the access token to fetch photo from Google Photos

Endpoints:

const {
  GOOGLE_CLIENT_ID: client_id,
  GOOGLE_CLIENT_SECRET: client_secret,
  GOOGLE_REFRESH_TOKEN: refresh_token,
  GOOGLE_PHOTOS_ALBUM_ID: album_id,
} = process.env

const token = Buffer.from(`${client_id}:${client_secret}`).toString('base64')
const TOKEN_ENDPOINT = 'https://oauth2.googleapis.com/token'
const PHOTOS_ENDPOINT = 'https://photoslibrary.googleapis.com/v1/mediaItems:search'

calling APIs

...
// OAuth Authorization: https://developers.google.com/identity/protocols/oauth2/native-app
const tokenResponse = await fetch(TOKEN_ENDPOINT, {
  method: 'POST',
  headers: {
    Authorization: `Basic ${token}`,
    'Content-Type': 'application/x-www-form-urlencoded',
  },
  body: `grant_type=refresh_token&refresh_token=${refresh_token}`,
  cache: 'no-cache',
})

const { access_token } = await tokenResponse.json()

// Fetch photos: https://developers.google.com/photos/library/reference/rest
const response = await fetch(PHOTOS_ENDPOINT, {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${access_token}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    pageSize: 100,
    albumId: album_id,
  }),
})

if (!response.ok) {
  throw new Error(`Failed to fetch photos: ${response.status}`)
}

const data = await response.json()
this.photos =
  data.mediaItems?.filter(
    (item) => item.baseUrl && item.mediaMetadata?.width && item.mediaMetadata?.height
  ) || []

console.log('Google Photos:', this.photos.length, 'photos loaded')

...

References