PROWARE technologies
PROWARE technologies

Wordpress REST API Example Using Node.js

THIS SECTION UNDER CONSTRUCTION

If not already familiar with what a REST API is then see the REST API Tutorial.

This example uses a Node.js server as a proxy between the browser and the Wordpress server. It uses Secure Sockets Layer since usernames and passwords are being sent.

Download all the code on this page plus the authorization utility plugin for the wordpress website: WORDPRESSAPI.zip

This is an example "key.pem" key file used for SSL websites.

-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAyLSUtz/Fw9vQ8PJyAuBKpV1iXeNRZbz5adDG171C9Oxa7Yse
L9paWboxE6Qgs9r6HOSLpUW5zMsXKJ108KzVGXkUVfjh1N4tzFjvQBL7b8A0u2al
4XxqzYQr53s/TJCVl/7KE9N73LxFIuaEW3KuavNB1X1tHjDqH6vjMoBPDFvHYruR
FmluvDOe1X9GUvHcrz0z5fGluViBQCdDueUhSvsP1dX5bLbjcxC8NOmA+TUnVd3G
JGVMhP2EZkJd444DAuEBDBo9RZPP3rOiKA/qZ+d0UmbNuwzg5a5wsxhPivt6jSwZ
grgJQzbAFPqhp9LQE9MqWhrQGlKKeti7cesP4QIDAQABAoIBAD4ky7dBo7ZVJYi1
SN/jGrnBBGgVrmpV7NP6tNav3z9+v1i1ro8aiMcng3KVaxrFPpQbn7F4uWLTr0QA
HOk4WnMcrlNAUIxcjsmq78ljFz8uwCUWT15V66vetMlju+G1EtyRI3ioDr31/WIl
Be5av+6Vi1jTESo78wIDcsF8QP2OEN78ldpmYa5ulhoeAmZrycjhjWniaxtReXrJ
LTcbelfLc5Jj80GRLoTPoGztSF5QYxH48Ub0sIBf1gX3cFILcZ2Ix0qWoqF7L6YG
xZcMipfZ7848+NILE71mkMy9KKrYPu+uNQvQDNZze/hO368eK04RY0oZJvc22vjt
OtOYKRECgYEA6+gKkm3HxYUiMhpkyBKo5NyfE7joFjYCcbgYqCeLtMlT3kkrzkhb
8tTQ+mVtWB7N1zuaIetR3qxwA0vv5YdY0NvLypMoE5KuWfl3lYmzEPOqfbyAYkfx
DEL1R+lij67TANZQH8yxpivcevZaStmCFNhkas0/Mt1vrtbNrS4jbacCgYEA2cz6
glSBnaYVdH5LO+/elsYCnpfrrIHqdoYONDgpqtZ1i7Q74rVHtlvDC/wFwakxP9Lb
zDDtL078V3+VWwW3fwvaXLtH4nF5Ggl8spG4UY019AJ8tgv2kPVaXJJtkbyOfVk8
TPMsLQN3TmhRNnv8hzOaIScRZQRlE3S0xEZ3lzcCgYEAoObXryApjdNMi5fs/Xmc
sNy5s53+zodwC1hhmO/AHkLjbU3DDyPTc0EseF0Nw3jfNNp2OoRihtpeXCFDMu6p
6WrBCR5ty0vUl5HLurb856cKtlAH0QD4rEBPRAkvLIk1afDDHa1jzc9ExqmY1Eye
qNEfTKlJXcwjDMF974z0pSkCgYAMvrTuCXnoOr7IqVdHs5qn4gBnPWaaGqQYuXF9
VsgBajlIqoxSkN8Yabg5LE897OOn43PfOAG5IfscN6gKwoPhWFU31fG364H1pxZ3
8JUDmKrOk5KuqHaONv1Jq3vj6k/AQAeKTTSJkclaejo+YPT/CGL9i6a17ZLol4y4
a5M18wKBgHvjN6DcYo+2aODm/6mkEb3wGuJcGsxt3SVjwd08VBaPivYspHmL95xF
Ka2NqmrITzloS3fbApKEBFudG/qV5fXuugeArvpVygktJUnLCBVGHLB+itXzTf4p
IJxJAOmc7Z1cZNqQdrHYyXO55Ci3+8X0GkFSFMHclS/mtrN41qDy
-----END RSA PRIVATE KEY-----

This is an example "cert.pem" certificate file used for SSL websites.

-----BEGIN CERTIFICATE-----
MIIDETCCAfkCFHIS9yzoLzLHzxoEjXtvBoFl4jTJMA0GCSqGSIb3DQEBCwUAMEUx
CzAJBgNVBAYTAlVTMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRl
cm5ldCBXaWRnaXRzIFB0eSBMdGQwHhcNMTkxMTA5MTUxMTM1WhcNNDcwMzI2MTUx
MTM1WjBFMQswCQYDVQQGEwJVUzETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UE
CgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOC
AQ8AMIIBCgKCAQEAyLSUtz/Fw9vQ8PJyAuBKpV1iXeNRZbz5adDG171C9Oxa7Yse
L9paWboxE6Qgs9r6HOSLpUW5zMsXKJ108KzVGXkUVfjh1N4tzFjvQBL7b8A0u2al
4XxqzYQr53s/TJCVl/7KE9N73LxFIuaEW3KuavNB1X1tHjDqH6vjMoBPDFvHYruR
FmluvDOe1X9GUvHcrz0z5fGluViBQCdDueUhSvsP1dX5bLbjcxC8NOmA+TUnVd3G
JGVMhP2EZkJd444DAuEBDBo9RZPP3rOiKA/qZ+d0UmbNuwzg5a5wsxhPivt6jSwZ
grgJQzbAFPqhp9LQE9MqWhrQGlKKeti7cesP4QIDAQABMA0GCSqGSIb3DQEBCwUA
A4IBAQCmF9Vf0QdWy7fLtFaBMKFX75ZMPd9g+OyhBwHSZN5WiRe40Q3b0yQFM/RW
BQ3A6/XsERA7G8g5hJIuc7bLzIq49S028oEUFQjcqFeSHj4xjmRm0NeCdLFjP3Ye
5ILNZzP+lkusNWapHDZXeBGyKxTMzNljF6ETONJQlrkfpakk0WtXjz4HQH1hDgh3
oAL7fLcf54CjVDjGdARgPDCHXMVCciorBlup7bHXS9zNznOJxu77TdawNHi1aw6l
wmV7qFp5K+czikUIUmKi3hlrM1SI0HMRX46HmKlwDThmJGrdgVtEOq3TJPRfxcjk
yYdlxxnsGyG9/54tRBsjwSrCAIdF
-----END CERTIFICATE-----

This is the "server.json" file used to store the Wordpress server name or ip address, a Wordpress administrator user name and password, and the server default document which is usually "index.html".

{
	"wordpress_server": "localhost",
	"wordpress_port": 80,
	"wordpress_user": "wordpress",
	"wordpress_pwd": "wordpress",
	"default_doc": "index.html"
}

This is the "server.js" file used for the Node.js secure HTTPS server. It very simply passes client requests on to the Wordpress server REST API and then returns the responses to the client (an HTML file using AJAX). There is a little extra logic here for serving static content like JavaScript, CSS and the HTML files to the browser. Notice that this code can be customized to increase security and provide custom routes.

// server.js
const fs = require("fs"); // require module "fs" (file system - for reading files)
const http = require("http"); // for accessing wordpress server
const https = require("https"); // for this server, an HTTPS one
const wp = JSON.parse(fs.readFileSync("server.json").toString()); // load and parse the server.json file synchronously
const opt = {
	key: fs.readFileSync("key.pem"), // load key.pem file
	cert: fs.readFileSync("cert.pem") // load cert.pem file
};
const port = process.env.PORT || 443; // 443 is HTTPS port
const server = https.createServer(opt).listen(port); // this line should be self explaining
console.log(`listening on port ${port}`);

var mime = {};
mime[".html"] = "text/html";
mime[".css"] = "text/css";
mime[".js"] = "application/javascript";
mime[".json"] = "application/json";
mime[".png"] = "image/png";
mime[".jpg"] = "image/jpeg";
mime[".ico"] = "image/x-icon";

function setOptions(hostname, port, path, clientReq) {
	var headers = {};
	for(var prop in clientReq.headers) { // set the request headers to the wordpress server based on the headers that the client browser sent
		if(!headers[prop]) {
			if(prop != "host" && prop != "origin" && prop != "referer" && prop != "authorization" && prop != "accept-encoding") { // ignore these headers
				headers[prop] = clientReq.headers[prop];
			}
		}
	}
	return {
		hostname: hostname,
		port: port,
		path: path,
		method: clientReq.method,
		headers: headers
	};
}

function httpRequest(options, requestBody, clientRes) {
	const req = http.request(options, function (res) {
		res.setEncoding("utf8");
		var resbody = "";
		res.on("data", function (data) {
			resbody += data.toString();
		}).on("end", function () {
			clientRes.writeHead(res.statusCode, res.headers);
			clientRes.end(resbody);
		});
	});
	req.on("error", function (err) {
		clientRes.writeHead(500, {"Content-Type":mime[".json"]});
		clientRes.end(JSON.stringify(err));
	});
	if(requestBody)
		req.write(requestBody); // forward the body of the request to the wordpress server
	req.end();
}

server.on("request", function (clientReq, clientRes) { // clientReq == client request and clientRes == client response
	var uri = clientReq.url;
	console.log(clientReq.method + ' ' + uri);
	if(clientReq.method == "GET") { // if GET request
		uri = uri.substring(1); // remove the leading slash "/"
		var params = uri.indexOf('?'); // query string? (querystrings are not used by this server; they are used by the client however)
		var file = uri.substring(0, params > 0 ? params : uri.length); // parse the file name if there is a file specified at all
		if(file.length == 0 || file.substring(file.length - 1) == '/') {
			file += wp.default_doc; // the default file for when a file is not specified
		}
		fs.readFile(file, function (err, data) { // NOTICE "readFile()" is used which is asynchronous function
			if(!err) {
				var ext = file.substring(file.lastIndexOf('.'), file.length); // get the file extension
				clientRes.writeHead(200, {"Content-Type": (mime[ext] || "text/plain")}); // set the header to OK (200)
				clientRes.end(data.toString("utf8"));
			}
			else { // assume it failed due to file not being found; should check the error in production code
				// if using IP address here and the wordpress server is using DHCP then double check its IP address because it can change
				const options = setOptions(wp.wordpress_server, wp.wordpress_port, `/${uri}`, clientReq);
				// must authorize for GET request, too!
				const cred = wp.wordpress_user + ':' + wp.wordpress_pwd; //credentials
				options.headers["Authorization"] = `Basic ${Buffer.from(cred).toString("base64")}`; // convert credentials to base64 string which is how the wordpress server expects it
				httpRequest(options, null, clientRes);
			}
		});
	}
	else if(clientReq.method == "POST" || clientReq.method == "PUT" || clientReq.method == "PATCH" || clientReq.method == "DELETE") {
		var body = "";
		clientReq.on("data", function (data) {
			body += data.toString();
		}).on("end", function () {
			const options = setOptions(wp.wordpress_server, wp.wordpress_port, uri, clientReq);
			if(clientReq.headers.authorization) { // if an authorization header was sent then use it
				try { // JSON.parse should be in a try block
					const login = JSON.parse(clientReq.headers.authorization); // of course it is formatted as JSON!!!
					const cred = login.username + ':' + login.password; // credentials
					options.headers["Authorization"] = `Basic ${Buffer.from(cred).toString("base64")}`; // convert credentials to base64 string
				}
				catch {}
			}
			httpRequest(options, body, clientRes);
		});
	}
	else {
		clientRes.writeHead(405, {"Content-Type":"text/plain"});
		clientRes.end(`method ${clientReq.method} not allowed`);
	}
});

This is the CSS file used for the formatting of the HTML pages so that they look not too disgusting.

body {
	background-color: #dfdfdf;
	color: #222;
	font-family: sans-serif;
}
a {
	text-decoration: none;
	background-color: inherit;
	color: inherit;
}
h3 {
	text-align: center;
}
.center {
	text-align: center;
	position: absolute;
	left: 50%;
	transform: translateX(-50%);
}
#items section {
	position: relative;
	padding: 5px;
	border: 1px solid #ccc;
	background-color: #f1f1f1;
	margin-top: 3px;
}
#items section div {
	text-align: left;
}
#items section a {
	color: blue;
}
#detail {
	position: absolute;
	visibility: hidden;
	opacity: 0;
	transition: 0.25s;
	top: 0;
	left: 0;
	min-width: 100%;
	min-height: 100%;
	background-color: #333;
	color: #eee;
	margin: 0;
}
#detail:target {
	visibility: visible;
	opacity: 1;
}

This is the "ajaxauth.js" file which has the very commonly used connect function for AJAX calls and authorization functions for saving user credentials.

// ajaxauth.js
function connect(method, action, headers, body, callbackok, callbackerr) { // "connect" is the AJAX function
	try {
		xmlobj = new XMLHttpRequest();
		xmlobj.onreadystatechange = function () {
			if (xmlobj.readyState == 4) {
				if (xmlobj.status >= 200 && xmlobj.status < 300) {
					if(callbackok) {
						callbackok(xmlobj.responseText);
					}
					return;
				}
				else {
					if(callbackerr) {
						callbackerr(xmlobj.status, xmlobj.responseText);
					}
				}
			}
		};
		xmlobj.open(method, action, true);
		for(var prop in headers) {
			xmlobj.setRequestHeader(prop, headers[prop]);
		}
		xmlobj.send(body);
	}
	catch {}
}
function setAuth(form) { // save the authorization information to local storage
	document.cookie = "auth=" + encodeURIComponent(JSON.stringify({ username: form.username.value, password: form.password.value }));
}
function getAuth() { // get the authorization information from local storage
	var cookies = document.cookie.split(";");
	for(var i = 0; i < cookies.length; i++) {
		var kv = cookies[i].trim().split("=");
		if(kv[0] == "auth") {
			return decodeURIComponent(kv[1]);
		}
	}
	return JSON.stringify({ username: "", password: "" });
}

This is the "helper.js" file which has functions for formatting the URL parameters (getParameters), printing an object (like the ones downloaded from the Wordpress REST API) used for a reference tool (printObject), removing all the children of a DOM object (removeChildren), and creating HTML elements to add to the DOM (createTag).

// helper.js
function printObject(obj, domobject, pretext) { // print the object to see what it looks like
	for(var prop in obj){
		var proptext = (isNaN(prop) ? "." + prop : "[" + prop + "]");
		if(typeof(obj[prop]) == "object"){
			printObject(obj[prop], domobject, pretext + proptext);
		}
		else {
			var text = pretext + proptext + "=" + (typeof(obj[prop]) == "string" ? '"' + obj[prop] + '"' : obj[prop]) + "\r\n";
			domobject.appendChild(document.createTextNode(text));
		}
	}
}
function getParameters(url) { // get the query string parameters of the request (client side)
	var a = document.createElement('a');
	a.href = url;
	return (function () {
		var seg = a.search.replace(/^\?/, '').split('&');
		var s, i = 0, len = seg.length, ret = {};
		for (; i < len; i++) {
			if (!seg[i]) { continue; }
			s = seg[i].split('=');
			if (s.length == 2) {
				ret[s[0]] = decodeURIComponent(s[1].replace(/\+/g, '%20'));
			} else if (s.length == 1) {
				ret[s[0]] = null;
			}
		}
		return ret;
	})();
}
function removeChildren(domobj) { // remove all the children elements from the specified Document Object Model element
	while(domobj.childNodes.length > 0) {
		domobj.removeChild(domobj.childNodes[0]);
	}
}
function createTag(tagName, innerHTML, parentNode) { // create HTML element by tag name (required), include HTML text, and parent element
	var e = document.createElement(tagName);
	e.innerHTML = innerHTML || "";
	if(typeof(parentNode) == "object") {
		parentNode.appendChild(e);
	}
	return e;
}

This is the "index.html" file which is mostly just a menu for access to the other HTML files, but it does allow for downloading and displaying Wordpress REST API objects and saving the user credentials.

<!DOCTYPE html>
<html lang="en">
<head>
	<title>WP EXAMPLES INDEX</title>
	<link rel="stylesheet" href="style.css" />
	<script src="ajaxauth.js"></script>
	<script src="helper.js"></script>
	<style>
	.menu {
		border: 1px solid #ccc;
		margin: 0;
		padding: 0;
		list-style: none;
		text-align: center;
		position: relative;
		background-color: #f1f1f1;
	}
	.menu li {
		display: inline-block;
	}
	.menu a {
		display: block;
		transition: .25s;
		padding: 10px 5px;
	}
	.menu a:hover {
		background-color: #ccc;
	}
	</style>
	<script>
	function restapi() {
		var action = prompt("ENTER REST API ROUTE:", "/index.php/wp-json");
		if(action) {
			var div = document.getElementById("div");
			removeChildren(div);
			for(var i = 0; i < 15; i++) {
				div.appendChild(document.createTextNode("\r\n"));
			}
			// user the connect function from the ajaxauth.js file
			connect("GET", action, null, null, function(txt) {
				var obj = JSON.parse(txt);
				console.log(obj);
				printObject(obj, div, "");
			}, function (stat, txt) {
				alert(stat); // alert the status code (200-500)
				printObject(JSON.parse(txt), div, "error"); // print the error object to see what the error is
			});
		}
		return false;
	}
	window.addEventListener("load", function () {
		var form = document.forms[0];
		var auth = JSON.parse(getAuth());
		form.username.value = auth.username;
		form.password.value = auth.password;
	}, false);
	</script>
</head>
<body>
<h3>WORDPRESS REST API EXAMPLE</h3>
<div class="center">
	<ul class="menu"><li>
		<a href="users.html">users</a>
	</li><li>
		<a href="posts.html">posts</a>
	</li><li>
		<a href="comments.html">comments</a>
	</li><li>
		<a onclick="return restapi();" href="#">rest api</a>
	</li></ul>
	<form>
		<fieldset>
			<legend>credentials</legend>
			<input type="text" name="username" placeholder="username" onblur="setAuth(this.form);" required /><br />
			<input type="password" name="password" placeholder="password" onblur="setAuth(this.form);" required /><br />
		</fieldset>
	</form>
</div>
<pre id="div"></pre>
</body>
</html>

This is the "users.html" file used for creating Wordpress users. It simply demonstrates how users are added to the Wordpress server's database using the REST API.

<!-- USERS.HTML -->
<!DOCTYPE html>
<html lang="en">
<head>
	<title>WP REST API EXAMPLE</title>
	<link rel="stylesheet" href="style.css" />
	<script src="ajaxauth.js"></script>
	<script src="helper.js"></script>
	<script>
	function onerror(stat, txt) { // on the event of an AJAX error, use this function
		var dtl = document.getElementById("detail");
		removeChildren(dtl);
		alert(stat);
		printObject(JSON.parse(txt), dtl, "error");
		location.href = "#detail";
	}
	function loginAndCreateUser(form) {
		var dtl = document.getElementById("detail");
		removeChildren(dtl);
		var headers = {"Authorization":getAuth(), "Content-Type":"application/json; charset=UTF-8"};
		var body = JSON.stringify({
			username: form.newusername.value,
			password: form.newpassword.value,
			email: form.newemail.value,
			roles: [form.role.value]
		});
		connect("POST", "/index.php/wp-json/wp/v2/users", headers, body, function () {
			form.reset();
		}, onerror);
		return false;
	}
	</script>
</head>
<body>
<div class="center">
	<a href="index.html">[index]</a>
	<form onsubmit="return loginAndCreateUser(this);">
		<fieldset>
			<legend>new user</legend>
			<select name="role" required>
				<option value="">(select role)</option>
				<option value="subscriber">subscriber</option>
				<option value="author">author</option>
				<option value="editor">editor</option>
				<option value="administrator">administrator</option>
			</select><br />
			<input type="text" name="newusername" placeholder="username" required /><br />
			<input type="text" name="newpassword" placeholder="password" required /><br />
			<input type="email" name="newemail" placeholder="email" required /><br />
			<input type="submit" value="add" />
		</fieldset>
	</form>
	<div id="items"></div>
</div>
<pre id="detail"></pre>
</body>
</html>

This is the "posts.html" file used for creating and viewing Wordpress blog posts using the REST API.

<!-- POSTS.HTML -->
<!DOCTYPE html>
<html lang="en">
<head>
	<title>WP REST API EXAMPLE</title>
	<link rel="stylesheet" href="style.css" />
	<script src="ajaxauth.js"></script>
	<script src="helper.js"></script>
	<script>
	function onerror(stat, txt) { // used in the event of an AJAX error
		var dtl = document.getElementById("detail");
		removeChildren(dtl);
		alert(stat);
		printObject(JSON.parse(txt), dtl, "error");
		location.href = "#detail";
	}
	function addBlogPostToDOM(post) { // add the wordpress blg post to the Document Object Model (DOM) of this HTML page
		// Angular or React could also have been used but it is good to learn what those tools are doing behind the scenes
		var sect = createTag("section");
		var a = createTag("a", "(delete)", sect); // DELETE POST LINK
		a.href = "javascript:;";
		a.id = post.id; // could have used setAttribute()
		a.onclick = function () {
			var dtl = document.getElementById("detail");
			removeChildren(dtl);
			var headers = {"Authorization":getAuth()};
			document.getElementById("items").removeChild(this.parentNode); // remove this links container
			connect("DELETE", "/index.php/wp-json/wp/v2/posts/" + this.id, headers, null, null, onerror); // the response to this action is the blog post that was deleted
		};
		a = createTag("a", "(comments)", sect); // VIEW COMMENTS LINK
		a.href = "comments.html?postid=" + post.id;
		createTag("h3", post.title.rendered + "<br />(slug: " + post.slug + ")", sect);
		createTag("div", post.content.rendered, sect);
		var items = document.getElementById("items");
		items.insertBefore(sect, items.childNodes[0]); // this will put the post first, use appendChild() to place the post last on the list
	}
	window.addEventListener("load", function () { // download the blog posts when the page loads
		var id = getParameters(location.href).id;
		connect("GET", "/index.php/wp-json/wp/v2/posts" + (id ? "/" + id : "?page=1&per_page=100"), null, null, function (txt) {
			var obj = JSON.parse(txt);
			if(Array.isArray(obj)) { // because this request can return an array of posts or just a single one
				for(var i = 0; i < obj.length; i++) {
					addBlogPostToDOM(obj[i]);
				}
			}
			else {
				addBlogPostToDOM(obj);
			}
		}, onerror);
	}, false);
	function loginAndPostBlogForm(form) {
		var headers = {"Authorization":getAuth(), "Content-Type":"application/json; charset=UTF-8"}; // set the authorization header to the logged in user (see index.html)
		var body = JSON.stringify({ title: form.title.value, content: form.content.value, status: "publish" }); // the body of hte POST is JSON text
		connect("POST", "/index.php/wp-json/wp/v2/posts", headers, body, function (txt) {
			addBlogPostToDOM(JSON.parse(txt)); // the wordpress server returns the new blog post object via the node.js server
			form.reset();
		}, onerror);
		return false;
	}
	</script>
</head>
<body>
<div class="center">
	<a href="index.html">[index]</a>
	<form onsubmit="return loginAndPostBlogForm(this);">
		<fieldset>
			<legend>new post</legend>
			<input type="text" name="title" placeholder="post title" required /><br />
			<textarea name="content" placeholder="post content" required></textarea><br />
			<input type="submit" value="add" required />
		</fieldset>
	</form>
	<div id="items"></div>
</div>
<pre id="detail"></pre>
</body>
</html>

This is the "comments.html" file used for creating and viewing Wordpress comments to blog posts using the REST API.

<!DOCTYPE html>
<!-- IT IS POSSIBLE TO HAVE BLOG COMMENTS ON THE SAME
	PAGE AS THE BLOG POSTS BUT IT WOULD HAVE RESULTED IN
	A LARGE, COMPLEX HTML FILE. SO COMMENTS HAVE THEIR
	OWN PAGE FOR SIMPLICITY'S SAKE (BETTER FOR LEARNING) -->
<html lang="en">
<head>
	<title>WP REST API EXAMPLE</title>
	<link rel="stylesheet" href="style.css" />
	<script src="ajaxauth.js"></script>
	<script src="helper.js"></script>
	<script>
	var params; // this file relies heavily on the querystring parameters
	function onerror(stat, txt) { // on the event of an AJAX error, use this function
		var dtl = document.getElementById("detail");
		removeChildren(dtl);
		alert(stat);
		printObject(JSON.parse(txt), dtl, "error"); // print the error object for the programmer
		location.href = "#detail";
	}
	function addCommentWithParentToDOM(comment, index, sect) { // use this function to add a "sub-comment" to the DOM
		var items = document.getElementById("items");
		if(items.childNodes[index]) {
			if(parseInt(items.childNodes[index].id) == comment.parent) {
				sect.style.left = parseInt(items.childNodes[index].style.left) + 30 + "px"; // move the comment over 30px to explain to the end user that it is a reply comment (relies on CSS properties)
				for(index++; items.childNodes[index] && items.childNodes[index].style.left == sect.style.left; index++);
				items.insertBefore(sect, items.childNodes[index]);
			}
			else {
				addCommentWithParentToDOM(comment, index + 1, sect) // this function is recursive
			}
		}
		else {
			items.appendChild(sect);
		}
	}
	function addCommentToDOM(comment) { // add the wordpress blog post's comment to the Document Object Model (DOM)
		var sect = createTag("section");
		sect.id = comment.id + "cid"; // could use setAttribute(), but decided to use the ID of the element
		var a;
		if(params.replyid == comment.id) { // then do not create a delete and reply link
			createTag("span", "(replying)", sect);
		}
		else { // otherwise, allow the user to delete and reply to the comment when it is not being replied to
			a = createTag("a", "(delete)", sect); // DELETE COMMENT LINK
			a.href = "javascript:;";
			a.onclick = function () { // when the user clicks the delete link, this code will run
				var dtl = document.getElementById("detail");
				removeChildren(dtl);
				var headers = {"Authorization": getAuth()};
				document.getElementById("items").removeChild(this.parentNode);
				connect("DELETE", "/index.php/wp-json/wp/v2/comments/" + parseInt(this.parentNode.id), headers, null, null, onerror); // the response is the comment object which is not needed
			};
			a = createTag("a", "(reply)", sect); // REPLY TO COMMENT LINK
			a.href = "comments.html?postid=" + comment.post + "&replyid=" + comment.id;
		}
		a = createTag("a", "(post)", sect); // VIEW POST LINK
		a.href = "posts.html?id=" + comment.post;
		createTag("div", comment.content.rendered, sect);
		var items = document.getElementById("items");
		sect.style.left = "0px";
		if(comment.parent) {
			addCommentWithParentToDOM(comment, 0, sect);
		}
		else {
			items.appendChild(sect);
		}
	}
	window.addEventListener("load", function () {
		params = getParameters(location.href); // very important! set the params variable first thing after the page is loaded
		// notice we are only retrieving the first 100 comments to keep the code simple, in production this will be inside a loop requesting each page of comments
		connect("GET", "/index.php/wp-json/wp/v2/comments?page=1&per_page=100", null, null, function (txt) {
			var comments = JSON.parse(txt);
			console.log(comments);
			comments.sort(function(obj1, obj2) { // put in ascending order just incase they are not
				if(obj1.id < obj2.id) return -1;
				if(obj1.id > obj2.id) return 1;
				return 0;
			});
			var postid = params.postid;
			/*
			if post id and (comment) id are present then show only the parent comment on the document
			if only post id is present then show all the comments for the related post
			if none are present then show all the comments
			*/
			if(postid) { // if showing comments for only one blog post then only display the ones with matching post id
				for(var i = 0; i < comments.length; i++) {
					if(comments[i].post == postid) {
						addCommentToDOM(comments[i]);
					}
				}
			}
			else { // otherwise show all the comments
				for(var i = 0; i < comments.length; i++) {
					addCommentToDOM(comments[i]);
				}
			}
			connect("GET", "/index.php/wp-json/wp/v2/posts" + (postid ? "/" + postid : "?page=1&per_page=100"), null, null, function (txt) {
				var obj = JSON.parse(txt);
				if(Array.isArray(obj)) { // then we have an array of blog posts
					var select = document.forms[0].post;
					var opt = createTag("option");
					opt.text = "select post for comment";
					opt.value = "";
					select.add(opt);
					for(var i = 0; i < obj.length; i++) {
						opt = createTag("option");
						opt.text = obj[i].title.rendered + " (slug: " + obj[i].slug + ")";
						opt.value = obj[i].id;
						if(obj[i].id == postid) {
							opt.selected = true;
						}
						select.add(opt);
					}
				}
				else { // otherwise we have a single blog post
					var opt = createTag("option");
					opt.text = obj.title.rendered + " (slug: " + obj.slug + ")";
					opt.value = obj.id;
					document.forms[0].post.add(opt);
				}
			}, onerror);
		}, onerror);
	}, false);
	function loginAndPostCommentForm(form) {
		var dtl = document.getElementById("detail");
		removeChildren(dtl)
		var headers = {"Authorization": getAuth(), "Content-Type": "application/json; charset=UTF-8"}; // set the authorization header to the logged user
		var body = JSON.stringify({ post: form.post.value, content: form.content.value, parent: params.replyid || 0, status: "publish" });
		connect("POST", "/index.php/wp-json/wp/v2/comments", headers, body, function (txt) {
			addCommentToDOM(JSON.parse(txt));  // the wordpress server returns the new comment object via the node.js server
			form.reset();
		}, onerror);
		return false;
	}
	</script>
</head>
<body>
<div class="center">
	<a href="index.html">[index]</a>
	<form onsubmit="return loginAndPostCommentForm(this);">
		<fieldset>
			<legend>new comment</legend>
			<select name="post" required></select><br />
			<textarea name="content" placeholder="comment content" required></textarea><br />
			<input type="submit" value="add" required />
		</fieldset>
	</form>
	<div id="items"></div>
</div>
<pre id="detail"></pre>
</body>
</html>

Related Video

https://youtu.be/