DEV Community

Agbanusi John
Agbanusi John

Posted on

FCC project 4 - Image Search Abstraction Layer

Now we will talk about the fourth project and how to go about it.
We're asked to build an app that can be used for image search and give us results according to our requests.

Project: Image Search Abstraction Layer,
Time taken: 3 hours,
Difficulty: Easy.
The link to the take home project is here
For this project we will be using glitch.

So, you start glitch choosing the hello-express project template after clicking on new project. We get a file structure like:

Alt Text

Then, we should try to edit the html file to a simple form page and style it however you want and the form has an action that send the values as a post request to the server on submission. Here's how mine looks;

Alt Text

So, let's go right to the back-end.
First let's require the desired modules, glitch made it easy for us to install modules, just go to package.json and click on add package and type the name of the package we want, a list of similar packages is shown and you click on the one you want to install. So, we write this in our server.js.

const express = require("express");
const app = express();
var mongo = require('mongodb').MongoClient;
const bodyParser=require('body-parser')
var request = require('request')

We're using express as default and then we use mongodb for our database and body-parser for easy url parsing, we are using request for sending a get request to an api. Note these packages aren't absolute, you might decide to use sql instead of mongodb among other packages too.

We'll be using google search API, I'll advise you to register to get your custom API. An example custom api is -

GET https://www.googleapis.com/customsearch/v1?key=INSERT_YOUR_API_KEY&cx=017576662512468239146:omuauf_lfve&q=

Also we have to set up some middlewares we need,

app.use(express.static("public"));
//app.use(helmet())
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));

Helmet is a package that helps secure your app from XSS attacks and more, but we'll not dwell on that here.
Now let's connect to our database.

mongo.connect(process.env.DB,{ useUnifiedTopology: true },(err,client)=>{
  var db=client.db('new')
  if (err) throw err;
})

"process.env.DB" refers to a secret api key we need to connect to my own database and is stored in the .env file.
Glitch automatically install the dotenv package for us, so we don't really need to install and require again. But just so you will know for other environments, you require and configure dotenv like this:

require('dotenv').config()

So, next let's create routes for our app, the first is the default path '/'.

app.get("/", (request, response) => {
  response.sendFile(__dirname + "/views/index.html");
});

Next, we create a route for a get request and parameters, then we get the compulsory parameters given with req.params.search in this case. The search term comes from the path and is made compulsory by putting a colon at the front of it. Some others may want to limit or offset their search to get a certain amount of images, though the default is 1, they could add an extra path by adding '?offset=number needed' and so we handled that on the third line using a ternary operator.

Now before we make the request to the API, let's save the search term inputted for later usage in our database, which is shown on the fifth line, so after saving in the database we then make a request to our custom API on the behalf of the user and specify a json response, then we go through the response and filter out what we don't need and return the first 1 or more items found depending on the offset query. Then we just send it as a json response to the user, we could also capture the data and show it any way we want but let's just stick with sending a json to the user.

app.get("/imagesearch/:search", (req, res) => {
  let search=req.params.search
  let page = req.query.offset? req.query.offset :1
  db.collection('img').insertOne({term:search,searched_on:new Date().toUTCString()},(err,doc)=>{
    request(ggle+search+'&searchType=image',{json:true},(err,red,data)=>{
      let dat=data.items.map((i)=>{return{image_url:i.link,alt_text:i.snippet,page_url:i.image.contextLink}})
      dat=dat.slice(0,page)
      res.send(dat);
    })
  })

Next, remember we also made a form to make it easy for users? we would have to do the same for a post request to the path you put as the form action. Also we have some little differences.

app.post("/api/imagesearch", (req, res) => {
let search=req.body.search
search= encodeURI(search)
let page = req.body.page ? req.body.page :1
})

We see that we use req.body instead of req.params or req.query here. Also because we're using a form, users could write two or more words and leave a space in between, url doesn't allow it so we have to change it to url format using encodeURI(form data).

Lastly, we also want users to see what others have searched for, so what we have to do is find all possible entries in that document and print them out. For this app we'll bring out the top ten.

app.get('/latest/imagesearch',(req,res)=>{
  db.collection('img').find({}).sort({searched_on:-1}).limit(10).toArray((err,doc)=>{
    res.send(doc)
  })
})

Then we lastly listen for changes, though this is already written for you if you're using glitch.

const listener = app.listen(3000, () => {
  console.log("Your app is listening on port " + 3000);
});

And voila. you're done! You can test my app here

Oldest comments (0)