User Flow:
http://localhost:3000
to http://localhost:3000/?code={code}
We need to access this code.
My current approach is to console log the response returned.
This is what the console logs:
https://docs.google.com/document/d/1FII3_xrjb6lmTkma20TDjoyRXN_yKZ-knmFgFyfALKc/edit?usp=sharing
Same console log, without numbering:
https://docs.google.com/document/d/12qeVkp82opa2ITk0wGs8Fv0_Zej38OzUkJGRh8TzFfE/edit?usp=sharing
Scroll to line 1259, you would find this:
params: {},
query:
{ code:
'AQC_G...nzDFS'
},
How can we access the value of this 'code'?
Parsing it to JSON online (hoping to get the path to 'query'), it says, "Failed to parse invalid JSON format."
Also, this is the app.js:
const express = require("express");
const https = require("https");
const bodyParser = require("body-parser");
const axios = require("axios");
const app = express();
app.listen(3000, function() {
})
// The page to load when the browser (client) makes request to GET something from the server on "/", i.e., from the homepage.
app.get("/", function(req, res) {
res.sendFile(__dirname + "/index.html");
console.log(res); // This is logging that long ServerResponse.
});
let authURL = "https://accounts.spotify.com/authorize?client_id={client_id}&response_type=code&redirect_uri=http%3A%2F%2Flocalhost%3A3000%2F&scope=user-read-playback-state%20app-remote-control%20user-modify-playback-state%20user-read-currently-playing%20user-read-playback-position%20user-read-email%20streaming"
// Redirect user to Spotify's endpoint.
app.post("/", function(req, res) {
res.redirect(authURL);
});
// The data that server should POST when the POST request is sent by the client, upon entering the search queryValue, in the search bar (form).
app.post("/", function(req, res) {
// The user input query. We are using body-parser package here.
const query = req.body.queryValue;
let searchUrl = "https://api.spotify.com/v1/search?q=" + query + "&type=track%2Calbum%2Cartist&limit=4&market=IN";
//Using Axios to fetch data. It gets parsed to JSON automatically.
axios.get(searchUrl, {
headers: {
'Authorization': token,
}
})
.then((resAxios) => {
console.log(resAxios.data)
//Extracting required data.
})
.catch((error) => {
console.error(error)
})
});
This is the index.js:
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="utf-8">
<title>Title</title>
<link rel="stylesheet" href="index.css">
</head>
<body>
<div class="index-search-container">
<form class="" action="/" method="post">
<input id="queryId" type="text" name="queryValue" value="" placeholder="Search">
<button type="submit" name="button">HIT ME</button>
</form>
</div>
</body>
</html>
Notice the 'POST' method 'form' in index.js. This form is actually used to send queryValue to the server, to be returned with some data fetched from API. In app.js, there are two app.post now, and only the first one works. Please help me improve the code. :)
Here is an example you can use to ask for a user permissions to contact Spotify's API on their behalf:
const express = require("express")
const bodyParser = require("body-parser")
const cookieParser = require("cookie-parser")
const axios = require("axios")
const crypto = require("crypto")
/**
* Environment Variables
*/
const PORT = process.env.PORT || 3000
const PROTOCOL = process.env.PROTOCOL || "http"
const URL = process.env.URL || `${PROTOCOL}://localhost:${PORT}`
const SEARCH_URL = process.env.SEARCH_URL || "https://api.spotify.com/v1/search"
const AUTH_URL = process.env.AUTH_URL || "https://accounts.spotify.com/authorize"
const TOKEN_URL = process.env.TOKEN_URL || "https://accounts.spotify.com/api/token"
const SCOPE = process.env.SCOPE || "user-read-playback-state app-remote-control user-modify-playback-state user-read-currently-playing user-read-playback-position user-read-email streaming"
/**
* Spotify's Client Secrets
*/
const CLIENT_ID = process.env.CLIENT_ID
const CLIENT_SECRET = process.env.CLIENT_SECRET
/**
* Memory Store
*/
const DB = {}
/**
* Express App Configuration
*/
const app = express()
app.use(bodyParser.json())
app.use(cookieParser())
app.use((_, res, next) => {
res.header('Access-Control-Allow-Origin', "*");
res.header('Access-Control-Allow-Headers', "*");
next()
})
// Home
app.get("/", (req, res) => {
res.status(200)
.header("Content-Type", "text/html")
.send(`
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Spotify Example</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<form id="searchForm">
<input id="queryId" type="text" name="query" value="" placeholder="Search">
<button type="submit" name="button">HIT ME</button>
</form>
<pre id="results">
<script>
const form$ = document.getElementById("searchForm")
const results$ = document.getElementById("results")
form$.addEventListener("submit", (e) => {
e.preventDefault()
fetch("/", {
method: "POST",
credentials: "same-origin",
headers: {"Content-Type": "application/json"},
body: JSON.stringify({query: e.target.elements.query.value})
})
.then((response) => {
if (response.status === 301) {
window.location.href = "/login"
return
}
return response.json()
})
.then((data) => results$.textContent = JSON.stringify(data, null, 2))
.catch((err) => console.error(err))
})
</script>
</body>
</html>
`)
})
// Search
app.post("/", (req, res) => {
const id = req.cookies.id
if (id === undefined || DB[id] === undefined) {
res.status(301).send()
return
}
axios({
url: `${SEARCH_URL}?q=${encodeURIComponent(req.body.query)}&type=track%2Calbum%2Cartist&limit=4&market=IN`,
method: "get",
headers: {"Authorization": `Bearer ${DB[id].access_token}`},
})
.then((response) => {
res.status(200).json(response.data)
})
.catch((err) => {
console.error(err)
res.status(400).send(err.message)
})
})
// Login
app.get("/login", (_, res) => {
res.redirect(`${AUTH_URL}?client_id=${CLIENT_ID}&scope=${encodeURIComponent(SCOPE)}&response_type=code&redirect_uri=${encodeURIComponent(URL + '/callback')}`)
})
// Callback
app.get("/callback", (req, res) => {
const id = crypto.randomBytes(9).toString("hex")
const code = req.query.code
axios({
url: TOKEN_URL,
method: "post",
data: Object.entries({grant_type: "client_credentials", code, redirect_uri: URL})
.map(([key, value]) => key + "=" + encodeURIComponent(value)).join("&"),
headers: {
"Accept": "application/json",
"Content-Type": "application/x-www-form-urlencoded",
},
auth: {username: CLIENT_ID, password: CLIENT_SECRET},
})
.then((response) => {
DB[id] = response.data
res.cookie("id", id, {maxAge: response.data.expires_in * 1000, httpOnly: true, path: "/"}).redirect("/")
})
.catch((err) => {
console.error(err)
res.status(400).send(err.message)
})
})
/**
* Listen
*/
app.listen(PORT, () => console.log(`- Listening on port ${PORT}`))
You need to provide your CLIENT_ID
, CLIENT_SECRET
, and the redirect URL
as environment variables. I suggest calling the index.js
script like this:
env CLIENT_ID=xxx \
env CLIENT_SECRET=zzz \
env URL=http://spotify.127.0.0.1.nip.io:3000 \
node index.js
The procedure is as follows:
redirect_uri
parameter.code
provided by Spotify, and we exchange it for the user's credentials by providing our CLIENT_ID
and SECRET_ID
.The next time the user runs a query, we'll have valid credentials, so we can query Spotify's API and return the results.