DEV Community

Cover image for How I made Instagram Content Fetching API in Node.js
Sahil Verma
Sahil Verma

Posted on • Edited on

How I made Instagram Content Fetching API in Node.js

GitHub Repo : instagram-api

So I was planning to build an Instagram downloading website. That's I was researching about this on Google, then I found this amazing trick.

If you put ?__a=1 after any Instagram link then it'll give a JSON in response.

For Instagram post

https://www.instagram.com/p/{post_id}/?__a=1
Enter fullscreen mode Exit fullscreen mode

For Instagram user profile

https://www.instagram.com/p/{profile_username}/?__a=1
Enter fullscreen mode Exit fullscreen mode

But if you try to fetch this API inside your code then, you'll get a CORS error like this.

cors error
To overcome this problem we have to use our server. So let's start building a node server for this project.

First, make a new folder and open that folder.

mkdir instagram-api
cd instagram-api
Enter fullscreen mode Exit fullscreen mode

Then initialize a node project.

npm init -y
Enter fullscreen mode Exit fullscreen mode

Now install the following dependencies.

npm i request express
Enter fullscreen mode Exit fullscreen mode

Make a new JavaScript file.

touch index.js
Enter fullscreen mode Exit fullscreen mode

Open VS Code in this folder.

code .
Enter fullscreen mode Exit fullscreen mode

Modify your scripts inside package.json file.

Before

"scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
},

Enter fullscreen mode Exit fullscreen mode

After

"scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "nodemon index.js" // you must have to install nodemon globally by npm install -g nodemon
},
Enter fullscreen mode Exit fullscreen mode

Open the index.js file in VS Code and import all dependencies.

const express = require("express");
const request = require("request");
Enter fullscreen mode Exit fullscreen mode

Now start listening to the port you want to.

const express = require("express");
const request = require("request");

const PORT = 1234;

const app = express();
app.listen(PORT, () => {
  console.clear();
  console.log(`listing in http://localhost:${PORT}`);
});
Enter fullscreen mode Exit fullscreen mode

You can start the server to see the results side by side.

npm start
Enter fullscreen mode Exit fullscreen mode

Listning

Now add a root route for testing and open the server in your browser.

app.get("/", (req, res) => {
  res?.send("I am working.");
});
Enter fullscreen mode Exit fullscreen mode

Testing

Add another route for Instagram posts.

app.get("/post/:id", (req, res) => {
  const { id } = req?.params;
  if (id) {
    res.send(`Post id = ${id}`);
  }
});
Enter fullscreen mode Exit fullscreen mode

Now I will use the request package to scrape the response data. It will scrape the data from Instagram API as a simple string and then I'll parse that string into JSON.

app.get("/post/:id", (req, res) => {
  const { id } = req?.params;
  if (id) {
    const link = `https://www.instagram.com/p/${id}/?__a=1`;
    request(link, (err, response, html) => {
      if (!err) {
        const json = JSON.parse(html);
        if (json) res.send(json);
      }
    });
  }
});
Enter fullscreen mode Exit fullscreen mode

And now if I look into my server with a valid Instagram post id I'll see something like this.

Response
I am getting a lot of data but I need only a certain type of data.
So I'll destructor it.

const json = JSON.parse(html)?.graphql?.shortcode_media;
res?.send({
  id,
  mainContent: {
    displayUrl: json?.display_url,
    resolutions: json?.display_resources.map((item) => ({
      src: item?.src,
    })),
  },
  user: {
    username: json?.owner?.username,
    isVerified: json?.owner?.is_verified,
    profilePic: json?.owner?.profile_pic_url,
  },
  caption: {
    main: json?.edge_media_to_caption.edges[0]?.node.text,
    accessibile: json?.accessibility_caption,
  },
  location: json?.location,
  comments: json?.edge_media_to_parent_comment?.count,
  likes: json?.edge_media_preview_like?.count,
  isVideo: json?.is_video,
  videoUrl: json?.video_url,
  allImages: json?.edge_sidecar_to_children?.edges?.map((item) => ({
    displayUrl: item?.node?.display_url,
    resolutions: item?.node?.display_resources?.map((item) => ({
      src: item?.src,
      size: item?.config_width,
    })),
  })),
});
Enter fullscreen mode Exit fullscreen mode

Now its much clean and it gives me what I need.

Response
You can also destructor it according to your needs.
I'll also do same for the user profile.

app.get("/user/:username", (req, res) => {
  const { username } = req?.params;
  if (username)
    const link = `https://www.instagram.com/${username}/?__a=1`;
    request(link, (err, response, html) => {
      if (!err) {
        const json = JSON.parse(html)?.graphql?.user;
        if (json)
          res?.send({
            username,
            bio: json.biography,
            profilePic: {
              sd: json?.profile_pic_url,
              hd: json?.profile_pic_url_hd,
            },
            fullName: json?.full_name,
            following: json?.edge_followed_by.count,
            follows: json?.edge_follow.count,
            posts: {
              total: json?.edge_owner_to_timeline_media?.count,
              content: json?.edge_owner_to_timeline_media?.edges?.map(
                (item) => ({
                  id: item?.node?.shortcode,
                  displayUrl: item?.node?.display_url,
                  caption: {
                    main: item?.node?.edge_media_to_caption.edges[0].node.text,
                    accessibile: item?.node?.accessibility_caption,
                  },
                  isVideo: item?.node?.is_video,
                  likes: item?.node?.edge_liked_by.count,
                  location: item?.node?.location,
                  comments: item?.node?.edge_media_to_comment.count,
                })
              ),
            },
          });
      }
    });
});
Enter fullscreen mode Exit fullscreen mode

Now it's time to use this API inside the frontend.
I'll use React JS as a frontend framework and axios for fetching API in this project.

I'll fetch this API and set images to images.
As you can see in this block of code.

useEffect(() => {
  const [images, setImages] = useState();
  const getData = async () => {
    const { data } = await axios("http://localhost:1234/post/CYa0_SRtUrf");
    if (data) {
      setImages(data);
      console.log(data);
    }
  };
  getData();
}, []);
Enter fullscreen mode Exit fullscreen mode

Now if I check the console there will be another CORS error.

cors error

To solve this error install another npm package called cros.

npm i cors
Enter fullscreen mode Exit fullscreen mode

Now import it and use it

const express = require("express");
const cors = require("cors");

const app = express();
app.use(cors());
Enter fullscreen mode Exit fullscreen mode

Now It will work.

response
But now, if I try to display images inside my webpage, it will again give me an error:

error
It is a bit difficult to deal with this error. But there are two ways to solve this problem. But I will show only the most convenient.

To solve this problem I'll convert to images I am getting from Instagram to base64.

If you don't know about base64 then it is.

Base64 is a group of binary-to-text encoding schemes that represent binary data (more specifically, a sequence of 8-bit bytes) in an ASCII string format by translating the data into a radix-64 representation. The term Base64 originates from a specific MIME content transfer encoding. Each non-final Base64 digit represents exactly 6 bits of data. Three bytes (i.e., a total of 24 bits) can therefore be represented by four 6-bit Base64 digits.
Source Wikipedia : Base64

To convert image to base64 first install another package called image-to-base64.

npm i image-to-base64
Enter fullscreen mode Exit fullscreen mode

Import image-to-base641 and make a new asynchronous function getBase64.

const imageToBase64 = require("image-to-base64");

const getBase64 = async (link) => {
  const base64 = await imageToBase64(link);
  return `data:image/jpeg;base64,${base64}`;
};
Enter fullscreen mode Exit fullscreen mode

This function will take the URL of the Instagram image and return it to the base64 code.
Since this is an asynchronous function, I need to wait for the response.
I have to change my code so that I get base64 in response.
Here is the final code.

const express = require("express");
const request = require("request");
const imageToBase64 = require("image-to-base64");
const cors = require("cors");

const PORT = 1234;

const app = express();
app.listen(PORT, () => {
  console.clear();
  console.log(`Listing in http://localhost:${PORT}`);
});

app.use(cors());

const getBase64 = async (link) => {
  const base64 = await imageToBase64(link);
  return `data:image/jpeg;base64,${base64}`;
};

app.get("/", (req, res) => {
  res?.send("I am working.");
});

app.get("/post/:id", (req, res) => {
  const { id } = req?.params;
  if (id) {
    const link = `https://www.instagram.com/p/${id}/?__a=1`;
    request(link, (err, response, html) => {
      if (!err) {
        const json = JSON.parse(html)?.graphql?.shortcode_media;
        const promiseArray = json?.edge_sidecar_to_children?.edges?.map(
          async (item) => ({
            displayUrl: await getBase64(item?.node?.display_url),
            resolutions: item?.node?.display_resources?.map((item) => ({
              src: item?.src,
              size: item?.config_width,
            })),
          })
        );
        let allImages;
        if (promiseArray) allImages = Promise.all(promiseArray);
        if (json) {
          (async () => {
            res?.send({
              id,
              mainContent: {
                displayUrl: await getBase64(json?.display_url),
                resolutions: json?.display_resources.map((item) => ({
                  src: item?.src,
                })),
              },
              user: {
                username: json?.owner?.username,
                isVerified: json?.owner?.is_verified,
                fullName: json?.owner?.full_name,
                profilePic: await getBase64(json?.owner?.profile_pic_url),
              },
              caption: {
                main: json?.edge_media_to_caption.edges[0]?.node.text,
                accessibile: json?.accessibility_caption,
              },
              location: json?.location,
              comments: json?.edge_media_to_parent_comment?.count,
              likes: json?.edge_media_preview_like?.count,
              isVideo: json?.is_video,
              videoUrl: json?.video_url,
              allImages: await allImages,
            });
          })();
        }
        if (!json) res?.status?.send("error");
      }
    });
  }
});
app.get("/user/:username", (req, res) => {
  const { username } = req?.params;
  if (username) {
    const link = `https://www.instagram.com/${username}/?__a=1`;
    request(link, (err, response, html) => {
      if (!err) {
        const json = JSON.parse(html)?.graphql?.user;
        const promiseArray = json?.edge_owner_to_timeline_media?.edges?.map(
          async (item) => ({
            displayUrl: await getBase64(item?.node?.display_url),
            id: item?.node?.shortcode,
            location: item?.node?.location,
            caption: {
              main: item?.node?.edge_media_to_caption.edges[0].node.text,
              accessibile: item?.node?.accessibility_caption,
            },
            comments: item?.node?.edge_media_to_comment.count,
            isVideo: item?.node?.is_video,
            likes: item?.node?.edge_liked_by.count,
            isCollection: item?.node?.edge_sidecar_to_children ? true : false,
            resolutions: item?.node?.thumbnail_resources,
          })
        );
        let allImages;
        if (promiseArray) allImages = Promise.all(promiseArray);
        if (json)
          (async () => {
            res?.send({
              username,
              bio: json.biography,
              isVerified: json?.is_verified,
              category: json?.category_name,
              externalURL: json?.external_url,
              profilePic: {
                sd: await getBase64(json?.profile_pic_url),
                hd: await getBase64(json?.profile_pic_url_hd),
              },
              fullName: json?.full_name,
              following: json?.edge_followed_by.count,
              follows: json?.edge_follow.count,
              posts: {
                total: json?.edge_owner_to_timeline_media?.count,
                content: await allImages,
              },
            });
          })();
        if (!json) res?.status(400).send("ERROR");
      }
    });
  }
  if (!username) res?.send(`Error`);
});
Enter fullscreen mode Exit fullscreen mode

Now if you try this API in the frontend it will perfectly work. I have build an Instagram Downloader web-app with the help of this API.

Here is the preview.
Post
Download Page
User
User Profile

End

Now let me tell you more about myself that it is my very first API. I don't know much about building an API. So if you don't like my code and want to give some feedback then feel free to suggest me in comments, and also check out my GitHub repo and contribute to make this API more useful.

Top comments (4)

Collapse
 
internettum profile image
Marius Nettum

Nice guide, but have you tested this on a server or just on localhost? It seems like instagram.com/${username}/?__a=1 requires a logged in Instagram user.

If I open instagram.com/sahilverma.dev/?__a=1 in a chrome tab where I'm logged into Instagram everything works fine, but if I open it in incognito I get redirected to instagram.com/accounts/login/

So the /user/:username endpoint will probably not work if you deploy your code to a server.

Collapse
 
sahilverma_dev profile image
Sahil Verma • Edited

I also want to host this API for testing. But I'm not able to host this API. That's why I haven't tested it on the server and didn't host my frontend which is also ready.
I am new in express and node and I'm still learning stuff.
But I have also tried API in the incognito mode, and it was working.

Collapse
 
727021 profile image
Andrew Schimelpfening

Nice article. In case you didn’t know, request has been deprecated for almost 2 years. You should consider using a different, supported http(s) library, like got or node-fetch.

Collapse
 
sahilverma_dev profile image
Sahil Verma

Thanx for your feedback. I really don't know about that. But I'll try it