The Basics of Creating a CRUD API with Plain Vanilla Node (No Frameworks)

Summary of RESTful Convention

Building an API

Setup

  • Must have nodeJS installed
  • create an empty folder and navigate terminal into it
  • create a server.js file and create an npm project touch server.js && npm init -y
// Import http library
const http = require("http")
// use env variable to define port with default
const PORT = process.env.PORT || 4000
//create our server object
const server = http.createServer()
// get the server to start listening
server.listen(PORT, err => {
// error checking
err ? console.error(err) : console.log(`listening on port ${PORT}`)
})

The Event Based Approach

// Import http library
const http = require("http")
// use env variable to define port with default
const PORT = process.env.PORT || 4000
//create our server object
const server = http.createServer()
// We define a function that runs in response to the request event
server.on("request", (request, response) => {
// handle request based on method then URL
switch (request.method) {
case "GET":
switch (request.url) {
// response for unexpected get requests
default:
response.statusCode = 400
response.write(`CANNOT GET ${request.url}`)
response.end()
}
break
case "POST":
break
case "PUT":
break
case "DELETE":
break
default:
// Send response for requests with no other response
response.statusCode = 400
response.write("No Response")
response.end()
}
})
// get the server to start listening
server.listen(PORT, err => {
// error checking
err ? console.error(err) : console.log(`listening on port ${PORT}`)
})

The Callback Approach

// Import http library
const http = require("http")
// use env variable to define port with default
const PORT = process.env.PORT || 4000
//create our server object, pass server function as callback argument
const server = http.createServer((request, response) => {
// handle request based on method then URL
switch (request.method) {
case "GET":
switch (request.url) {
// response for unexpected get requests
default:
response.statusCode = 400
response.write(`CANNOT GET ${request.url}`)
response.end
}
break
case "POST":
break
case "PUT":
break
case "DELETE":
break
default:
// Send response for requests with no other response
response.statusCode = 400
response.write("No Response")
response.end()
}
})
// get the server to start listening
server.listen(PORT, err => {
// error checking
err ? console.error(err) : console.log(`listening on port ${PORT}`)
})

Let’s Simplify This

  • touch get.js post.js put.js delete.js
module.exports = (request, response) => {
switch (request.url) {
// response for unexpected get requests
default:
response.statusCode = 400
response.write(`CANNOT GET ${request.url}`)
response.end()
}
}
module.exports = (request, response) => {
switch (request.url) {
// response for unexpected get requests
default:
response.statusCode = 400
response.write(`CANNOT POST ${request.url}`)
response.end()
}
}
module.exports = (request, response) => {
switch(request.url){
// response for unexpected get requests
default:
response.statusCode = 400
response.write(`CANNOT PUT ${request.url}`)
response.end()
}
}
module.exports = (request, response) => {
switch (request.url) {
// response for unexpected get requests
default:
response.statusCode = 400
response.write(`CANNOT DELETE ${request.url}`)
response.end()
}
}
// Import http library
const http = require("http")
// use env variable to define port with default
const PORT = process.env.PORT || 4000
// Import our routers
const get = require("./get")
const post = require("./post")
const put = require("./put")
// add an extra R since delete is a reserved word
const deleteR = require("./delete")
//create our server object, pass server function as callback argument
const server = http.createServer((request, response) => {
// handle request based on method then URL
switch (request.method) {
case "GET":
get(request, response)
break
case "POST":
post(request, response)
break
case "PUT":
put(request, response)
break
case "DELETE":
deleteR(request, response)
break
default:
// Send response for requests with no other response
response.statusCode = 400
response.write("No Response")
response.end()
}
})
// get the server to start listening
server.listen(PORT, err => {
// error checking
err ? console.error(err) : console.log(`listening on port ${PORT}`)
})

Our Dataset

  • create a file called data.js with the following
module.exports = [{ title: "The first post", body: "body of the first post" }]
// Import http library
const http = require("http")
// use env variable to define port with default
const PORT = process.env.PORT || 4000
// import data
const posts = require("./data")
// Import our routers
const get = require("./get")
const post = require("./post")
const put = require("./put")
// add an extra R since delete is a reserved word
const deleteR = require("./delete")
//create our server object, pass server function as callback argument
const server = http.createServer((request, response) => {
// add the data to the request object so our routes can access it
request.posts = posts
// handle request based on method then URL
switch (request.method) {
case "GET":
get(request, response)
break
case "POST":
post(request, response)
break
case "PUT":
put(request, response)
break
case "DELETE":
deleteR(request, response)
break
default:
// Send response for requests with no other response
response.statusCode = 400
response.write("No Response")
response.end()
}
})
// get the server to start listening
server.listen(PORT, err => {
// error checking
err ? console.error(err) : console.log(`listening on port ${PORT}`)
})

Index Route

module.exports = (request, response) => {
switch (request.url) {
case "/posts":
response.statusCode = 200
response.setHeader("Content-Type", "application/json")
response.write(JSON.stringify(request.posts))
response.end()
break
// response for unexpected get requests
default:
response.statusCode = 400
response.write(`CANNOT GET ${request.url}`)
response.end()
}
}

The Show Route

// Import http library
const http = require("http");
// use env variable to define port with default
const PORT = process.env.PORT || 4000;
// import the url standard library for parsing query string
require("url")
// import data
const posts = require("./data");
// Import our routers
const get = require("./get");
const post = require("./post");
const put = require("./put");
// add an extra R since delete is a reserved word
const deleteR = require("./delete");
//create our server object, pass server function as callback argument
const server = http.createServer((request, response) => {
// add the data to the request object so our routes can access it
request.posts = posts
// adding the query to the request object
request.query = new URL(request.url, `http://${request.headers.host}`)
// handle request based on method then URL
switch (request.method) {
case "GET":
get(request, response);
break;
case "POST":
post(request, response);
break;
case "PUT":
put(request, response);
break;
case "DELETE":
deleteR(request, response);
break;
default:
// Send response for requests with no other response
response.statusCode = 400;
response.write("No Response");
response.end();
}
});
// get the server to start listening
server.listen(PORT, (err) => {
// error checking
err ? console.error(err) : console.log(`listening on port ${PORT}`);
});
module.exports = (request, response) => {    // remove queries from the url, turn "/posts?id=0" into "/posts"
const url = request.url.split("?")[0]
switch(url){ case "/posts":
// if the id query is present return the show result
if (request.query.searchParams.get("id")){
const id = request.query.searchParams.get("id")
response.statusCode = 200
response.setHeader("Content-Type", "application/json")
response.write(JSON.stringify(request.posts[id]))
response.end()
} else {
// else return all posts (index)
response.statusCode = 200
response.setHeader("Content-Type", "application/json")
response.write(JSON.stringify(request.posts))
response.end()
}
break
// response for unexpected get requests
default:
response.statusCode = 400
response.write(`CANNOT GET ${request.url}`)
response.end()
break
}
}

The Create Route

  • create a getBody.js with the function that’ll act like a traditional middleware handling the request/response object then passing it to the next function in line.
module.exports = (request, response, next) => {
let data = []
// assemble stream of data from request body
request.on("data", dataChunk => {
data.push(dataChunk)
})
request.on("end", () => {
request.body = Buffer.concat(data).toString()
if (request.headers["content-type"] === "application/json"){
request.body = JSON.parse(request.body)
}
// move on to next step in handling respone
next(request, response)
})
}
// Import http library
const http = require("http");
// use env variable to define port with default
const PORT = process.env.PORT || 4000;
// import the url standard library for parsing query string
require("url")
// import data
const posts = require("./data");
// Import our routers
const get = require("./get");
const post = require("./post");
const put = require("./put");
// add an extra R since delete is a reserved word
const deleteR = require("./delete");
// require function to parse body
const getBody = require("./getBody")
//create our server object, pass server function as callback argument
const server = http.createServer((request, response) => {
// add the data to the request object so our routes can access it
request.posts = posts
// adding the query to the request object
request.query = new URL(request.url, `http://${request.headers.host}`)
// handle request based on method then URL
switch (request.method) {
case "GET":
getBody(request, response, get);
break;
case "POST":
getBody(request, response, post);
break;
case "PUT":
getBody(request, response, put);
break;
case "DELETE":
getBody(request, response, deleteR);
break;
default:
// Send response for requests with no other response
response.statusCode = 400;
response.write("No Response");
response.end();
}
});
// get the server to start listening
server.listen(PORT, (err) => {
// error checking
err ? console.error(err) : console.log(`listening on port ${PORT}`);
});
module.exports = (request, response) => {
switch (request.url) {
case "/posts":
request.posts.push(request.body);
response.statusCode = 200;
response.setHeader("Content-Type", "application/json");
response.write(JSON.stringify(request.posts));
response.end();
break;
// response for unexpected get requests
default:
response.statusCode = 400;
response.write(`CANNOT POST ${request.url}`);
response.end();
}
};

Update Route

module.exports = (request, response) => {    // remove queries from the url, turn "/posts?id=0" into "/posts"
const url = request.url.split("?")[0]
switch(url){ case "/posts":
const id = request.query.searchParams.get("id")
response.statusCode = 200
response.setHeader("Content-Type", "application/json")
request.posts[id] = request.body
response.write(JSON.stringify(request.posts[id]))
response.end()
break
// response for unexpected get requests
default:
response.statusCode = 400
response.write(`CANNOT PUT ${request.url}`)
response.end()
break
}
}

Destroy Route

module.exports = (request, response) => {
// remove queries from the url, turn "/posts?id=0" into "/posts"
const url = request.url.split("?")[0];
switch (url) {
case "/posts":
const id = request.query.searchParams.get("id");
response.statusCode = 200;
response.setHeader("Content-Type", "application/json");
request.posts.splice(id, 1);
response.write(JSON.stringify(request.posts));
response.end();
break;
// response for unexpected get requests
default:
response.statusCode = 400;
response.write(`CANNOT DELETE ${request.url}`);
response.end();
break;
}
};

Conclusion

  • creating routing params
  • setting up cors headers
  • being able to parse urlEncoded or XML bodies
  • adding https support with the “https” library

--

--

--

Alex Merced is a Full Stack Developer, learn more about his work at AlexMercedCoder.com

Love podcasts or audiobooks? Learn on the go with our new app.

Recommended from Medium

Coding Interview for web developers

Redux Action Creator

Connecting APIs and web apps using Node.js

Editing Images with Convolution Matrices using Node.js and JIMP

Why and How to Avoid Await in a For-Loop

Using propType declarations to document your React components

Song Re-Writer

Graph Data Structure Implementation in JavaScript

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Alex Merced Coder

Alex Merced Coder

Alex Merced is a Full Stack Developer, learn more about his work at AlexMercedCoder.com

More from Medium

How to make a Vue Router 4 Navigation Guard with Firebase v9 API and Pinia

React Admin: Introduction

Astro — Ship less JavaScript with this Static Site Bundler

ship less javascript with astro

Developing Face Detection Application — Using Azure Cognitive Services Face API & React