Pic placeholder is a stylish image placeholder with 6 categories (animals, cats, dogs, houses, landscapes & people) which collectively sums up to 500+ images.
Here's a short video that explains the project and how it uses redis:
Pic placeholder is a microservice that provides endpoints to get placeholder images. These endpoints are:
/api/categories/[type]
- returns JSON of all images with the supported types filtered from redis
/api/images/[id]
- redirects to specific image stored on aws S3
/api/images
- returns JSON of all images from redis
/api/random/[category]
- redirects to a random image matching the category
/api/random
- redirects to a random image
There are two parts to this; the Redis JSON storage and the aws s3 storage.
-
The Redis JSON contains the placeholders which are of this schema:
{ file: { type: "number" }, width: { type: "number" }, height: { type: "number" }, post_link: { type: "string" }, author: { type: "string" }, type: { type: "text" }, },
- file: name of the image in s3
- widht, height: original image dimensions
- post_link: location of unsplash image location
- author: unsplash image creator's name
- type: one of the categories
-
S3 storage: This is where the actual image files are stored referencing the file field from the above schema.
This is how the data is accessed for each endpoint using the two storage providers above:
/api/categories/[type]
- This returns a JSON, so it goes to the redis, uses redis search to filter the json data by provided category, it's JSON is then updated to point to the location of each individual object and is then returned
// temp being the schema above, return { ...temp, image: `https://picc.vercel.app/api/images/${temp.file}` }
- This returns a JSON, so it goes to the redis, uses redis search to filter the json data by provided category, it's JSON is then updated to point to the location of each individual object and is then returned
/api/images/[id]
- This maps the incoming request path to the s3 bucket path that points to
id.jpg
in the bucket. i.ehttps://picc.vercel.app/api/images/1
=>https://bucket_url/images/1.jpg
- This maps the incoming request path to the s3 bucket path that points to
/api/images
- This returns a JSON of all placeholders stored in redis with alterations like the first endpoint above.
/api/random/[category]
- This takes the category as a query and searches redis just like
/api/categories/[type]
with an added step of picking a random image from the returned array and returns a response redirect
- This takes the category as a query and searches redis just like
/api/random
- This picks a random value from the range of the image collection (0-538) and then appends it the image location url as a redirect. i.e:
res.redirect(`https://picc.vercel.app/api/images/${file}`)
- This picks a random value from the range of the image collection (0-538) and then appends it the image location url as a redirect. i.e:
- Node (min. v14)
We're going to be doing the following:
- Use Unsplash API to get Images
- Use Node JS to generate the JSON & download the images
- Use AWS S3 to store the image files
- Use Redis to store the JSON files
- Integrate it all with our next app
- I used this to generate the JSON files and the first step is get an unsplash api key, more information can be found in this link
- After following all the steps in the Getting started section of the readme, continue below
- Now while still in the
unsplash-images-json project
, replace the content of theindex.js
with this:
require('dotenv').config();
const fs = require('fs');
const nodeFetch = require('node-fetch');
const path = require('path');
const prompt = require('prompt-sync')();
const { createApi } = require('unsplash-js');
const { shuffleArray } = require('./utils');
const dataDirectory = path.resolve(__dirname, '..', 'data'); // save photos JSON files here
const jsonFilePath = (name) => path.join(dataDirectory, name);
if (!fs.existsSync(dataDirectory)) {
fs.mkdirSync(dataDirectory);
}
// Create an instance of the Unsplash API
const unsplash = createApi({
accessKey: process.env.ACCESS_KEY,
fetch: nodeFetch,
});
// Search for photos
async function fetchPhotos(query, transformResult, page) {
try {
const data = await unsplash.search.getPhotos({
query,
page: page,
perPage: 30,
orientation: 'landscape',
});
if (data.errors) {
console.log('[FETCHING PHOTOS ERROR]: ', data.errors[0]);
} else {
const { results } = data.response;
const output = transformResult
? results.map((result) => {
return {
file: result.id,
width: result.width,
height: result.height,
img: result.urls.full,
post_link: result.links.html,
author: result.user.name,
};
})
: results;
console.log(`Finished fetching ${query} photos!`);
return output;
}
} catch (error) {
console.log('[ERROR OCCURRED]: ', error);
}
}
// Fetch various photos
async function fetchListOfVariousPhotos(query, transform) {
try {
const fetchedResults = [];
fetchedResults.push(fetchPhotos(query.term, transform, query.page));
const results = await Promise.all(fetchedResults);
const list = [].concat.apply([], results);
const shuffledList = [...list]; // create a copy of the list of photos
shuffleArray(shuffledList); // Shuffle the list of photos
fs.createWriteStream(jsonFilePath(query.name)).write(
JSON.stringify(shuffledList, null, 2)
);
} catch (error) {
console.log('[FETCHING VARIOUS PHOTOS ERROR]: ', error);
}
}
// Example search terms: architecture, textures patterns, galaxy
const searchTerm = prompt(
'Enter any word to start process: '
);
if (searchTerm) {
const qq = [
{
term: 'animals',
name: 'animals.json',
page: 1
},
{
term: 'animals',
name: 'animals2.json',
page: 2
},
{
term: 'animals',
name: 'animals3.json',
page: 3
},
{
term: 'cats',
name: 'cats.json',
page: 1
},
{
term: 'cats',
name: 'cats2.json',
page: 2
},
{
term: 'cats',
name: 'cats3.json',
page: 3
},
{
term: 'dogs',
name: 'dogs.json',
page: 1
},
{
term: 'dogs',
name: 'dogs2.json',
page: 2
},
{
term: 'dogs',
name: 'dogs3.json',
page: 3
},
{
term: 'people',
name: 'people.json',
page: 1
},
{
term: 'people',
name: 'people2.json',
page: 2
},
{
term: 'people',
name: 'people3.json',
page: 3
},
{
term: 'houses',
name: 'houses.json',
page: 1
},
{
term: 'houses',
name: 'houses2.json',
page: 2
},
{
term: 'houses',
name: 'houses3.json',
page: 3
},
{
term: 'landscapes',
name: 'landscapes.json',
page: 1
},
{
term: 'landscapes',
name: 'landscapes2.json',
page: 2
},
{
term: 'landscapes',
name: 'landscapes3.json',
page: 3
},
]
qq.forEach((query) => fetchListOfVariousPhotos(query, true) )
}
Running the above with yarn start
effectively generates the 500+ JSON used for this project.
- The next step after the generation is done is to alter the JSON to match the schema defined in the earlier sections and download the images using this script:
const fs = require('fs'),
https = require("https"),
Q = require("q");
const animals1 = require('../data/animals.json')
const animals2 = require('../data/animals2.json')
const animals3 = require('../data/animals3.json')
const cats1 = require('../data/cats.json')
const cats2 = require('../data/cats2.json')
const cats3 = require('../data/cats3.json')
const dogs1 = require('../data/dogs.json')
const dogs2 = require('../data/dogs2.json')
const dogs3 = require('../data/dogs3.json')
const houses1 = require('../data/houses.json')
const houses2 = require('../data/houses2.json')
const houses3 = require('../data/houses3.json')
const landscapes1 = require('../data/landscapes.json')
const landscapes2 = require('../data/landscapes2.json')
const landscapes3 = require('../data/landscapes3.json')
const people1 = require('../data/people.json')
const people2 = require('../data/people2.json')
const people3 = require('../data/people3.json')
let animals = [
...animals1,
...animals2,
...animals3,
]
let cats = [
...cats1,
...cats2,
...cats3,
]
let dogs = [
...dogs1,
...dogs2,
...dogs3,
]
let houses = [
...houses1,
...houses2,
...houses3,
]
let landscapes = [
...landscapes1,
...landscapes2,
...landscapes3,
]
let people = [
...people1,
...people2,
...people3,
]
const addType = (array, category) => {
return array.map((item) => ({
...item,
type: category
}))
};
animals = addType(animals, 'animals')
cats = addType(cats, 'cats')
dogs = addType(dogs, 'dogs')
houses = addType(houses, 'houses')
landscapes = addType(landscapes, 'landscapes')
people = addType(people, 'people')
let data = [
...animals,
...cats,
...dogs,
...houses,
...landscapes,
...people,
];
function shuffle(array) {
let currentIndex = array.length, randomIndex;
// While there remain elements to shuffle.
while (currentIndex != 0) {
// Pick a remaining element.
randomIndex = Math.floor(Math.random() * currentIndex);
currentIndex--;
// And swap it with the current element.
[array[currentIndex], array[randomIndex]] = [
array[randomIndex], array[currentIndex]];
}
return array;
}
shuffle(data)
data = data.map((item, i) => ({
...item,
file: i
}))
//create temp image JSONs for next processes
fs.createWriteStream('imagesAlt.json').write(
JSON.stringify(data, null, 2)
);
function downloadImage(url, filepath) {
var fileStream = fs.createWriteStream(filepath),
deferred = Q.defer();
fileStream
.on("open", function () {
https.get(url, function (res) {
res.on("error", function (err) {
deferred.reject(err);
});
res.pipe(fileStream);
});
})
.on("error", function (err) {
deferred.reject(err);
})
.on("finish", function () {
deferred.resolve(filepath);
});
return deferred.promise;
}
// create images in folder for s3 upload
data.forEach((item, i) => {
if(m.includes(i)) {
downloadImage(item.img, `./images/${i}.jpg`)
}
})
const deleteImgKeyFromObject = (array) => array.map(({img, ...items}) => items)
const newData = deleteImgKeyFromObject(data)
// create image JSONs for redis storage
fs.createWriteStream('images.json').write(
JSON.stringify(newData, null, 2)
);
Running the above with node main
will generate the necessary JSON and images we need to move forward (might take a while depending on your network and proceessing speed).
- Now we need to create our S3 bucket, while creating it uncheck this box, for public access
- And after its creation go the bucket
permissions
and update the bucket policy with this JSON:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AddPerm",
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::BUCKET_NAME/*"
}
]
}
- After that is done click, the upload button and upload the
images
folder from theunsplash-images-json
project directory. - Now all images should be publicly readable. NB: remeber to store the S3 url for later (you can always come back and get it).
- Firstly we copy the
image.json
from ourunsplash-images-json
project to this project - Next we create a free database on redis cloud. NB rememeber to store password and host for the created db shown in the dashboard for later.
- Now we do
yarn
ornpm install
to install dependencies - If you followed the above correctly you should have two variables similar to the
.env-sample
in this project - Add the variables to your
.env.local
- Followed by
yarn dev
ornpm dev
to start localhost - So, Finally we need to add the image placeholders and index them in our redis db:
- The first step is to run the
handleCreatePlaceholder
in ourindex.js
, (currently it's commented out), it can be run by using it as theonClick
for thetry it
button - After the process we index the database by going to this url in our browser;
http://localhost:3000/api/createIndex
- The first step is to run the
To make deploys work, you need to create free account on Redis Cloud
Be sure to follow all the steps above as well as editing the vercel env variables
Vercel
Here some resources to help you quickly get started using Redis Stack. If you still have questions, feel free to ask them in the Redis Discord or on Twitter.
- Sign up for a free Redis Cloud account using this link and use the Redis Stack database in the cloud.
- Based on the language/framework you want to use, you will find the following client libraries:
- Redis OM .NET (C#)
- Watch this getting started video
- Follow this getting started guide
- Redis OM Node (JS)
- Watch this getting started video
- Follow this getting started guide
- Redis OM Python
- Watch this getting started video
- Follow this getting started guide
- Redis OM Spring (Java)
- Watch this getting started video
- Follow this getting started guide
- Redis OM .NET (C#)
The above videos and guides should be enough to get you started in your desired language/framework. From there you can expand and develop your app. Use the resources below to help guide you further:
- Developer Hub - The main developer page for Redis, where you can find information on building using Redis with sample projects, guides, and tutorials.
- Redis Stack getting started page - Lists all the Redis Stack features. From there you can find relevant docs and tutorials for all the capabilities of Redis Stack.
- Redis Rediscover - Provides use-cases for Redis as well as real-world examples and educational material
- RedisInsight - Desktop GUI tool - Use this to connect to Redis to visually see the data. It also has a CLI inside it that lets you send Redis CLI commands. It also has a profiler so you can see commands that are run on your Redis instance in real-time
- Youtube Videos
- Official Redis Youtube channel
- Redis Stack videos - Help you get started modeling data, using Redis OM, and exploring Redis Stack
- Redis Stack Real-Time Stock App from Ahmad Bazzi
- Build a Fullstack Next.js app with Fireship.io
- Microservices with Redis Course by Scalable Scripts on freeCodeCamp