- Published on
Using Google Photos API in Next.js Blog

TOC
This setup enables using personal Google Photos album as a banner image source for Next.js blog posts.
Google Cloud Project Configuration
- Create project in Google Cloud Console
- Set up OAuth credentials:
- API & Services → Create Credentials
- Select "Desktop app"
- 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')
...