A Vanilla Node.js REST API without Frameworks such us Express

Node.js is a very popular JavaScript framework. It shines most when used to build back-end services and APIs. Node.js developers often utilize open-source frameworks and libraries like Express.js to develop applications. These libraries are readily available in the NPM registry.

Whenever you use such packages, there is a lot of abstraction; hence you don’t utilize the core functionalities of Node.js. The low-level logic Node.js of your application is hidden from you as these packages process and execute raw Node.js behind the scenes.

One key thing to note is that these packages make Node.js a popular technology. On the other side, you can opt to use core Node.js to develop your applications. This way, you make use of the Vanilla Node.js functionalities. This blog will teach you how to use vanilla Node.js with no frameworks to build simple APIs.

Goal

In this guide, we’ll build a simple REST API using functionalities core to Node.js itself. We’re just using the bare-bones of vanilla Node.js with the HTTP module for creating and managing a server.

This means we won’t use NPM. Therefore no NPM associated dependencies, no package.json, no package-lock.json, and no node_module folder.

The goal is to show you how Node.js works in its purest form and how you may use it without external libraries or frameworks.

Note: When working on any actual project, it is best to use Node.js libraries and packages. This way, you’ll take full advantage of the readily available code to make your development workflow easier and faster.

Prerequisites

  • Ensure you have Node.js and Postman installed on your computer.
  • Be familiar with how to use Postman.
  • Basic knowledge of Node.js.
  • Basic understanding of REST APIs and CRUD operations.
  • Basic understanding of JavaScript. This guide uses ES6 features and syntax, such as the arrow function (=>).

Setting up a simple HTTP server

Before creating the REST API, let’s create a simple HTTP API to serve a Hi there statement.

Create a project folder and an app.js file.

  1. The first thing to do is to pull the HTTP module from Node.js using require() method. This module is native to Node.js. You don’t need any extra packages or libraries to access it, just Node.js runtime installed on your computer.

const

http

=

require

(

"http"

);

This way, we make the necessary methods and functions available to set up a server.

  1. Once available, define the port you want the server to run on, as shown below.

const

PORT

=

process

.

env

.

PORT

||

5000

;
  1. To create the server, you need to call the createServer method from the HTTP module. i.e, http.createServer. Pass a response and a request that serves up your information.

Then use:

  • req.url to set the request access route/URL.
  • req.method.
  • res.writeHead to set any response headers.
  • res.write() to send the actual content for the response.
  • res.end() to end the response.

const

server

=

http

.

createServer

(

async

(

req

,

res

) => {

//set the request route

if

(

req

.

url

===

"/api"

&&

req

.

method

===

"GET"

) {

//response headers

res

.

writeHead

(

200

, {

"Content-Type"

:

"application/json"

});

//set the response

res

.

write

(

"Hi there, This is a Vanilla Node.js API"

);

//end the response

res

.

end

();

}

// If no route present

else

{

res

.

writeHead

(

404

, {

"Content-Type"

:

"application/json"

});

res

.

end

(

JSON

.

stringify

({

message

:

"Route not found"

}));

}

});

  1. Call the listen() method and pass in the PORT variable. Then add a console.log() message that will indicate the server is up and running.

server

.

listen

(

PORT

, () => {

console

.

log

(

`server started on port:

${

PORT

}

`

);

});

  1. The server is well set. Run node app.js to test it. This will log the console.log() message on your command screen.

A simple server api

  1. If you open http://localhost:5000/api on a browser, you’ll be served the response as defined in the res.write()

A simple server response

Setting up the REST API

Let’s now see how to set up a REST API using raw Node.js. We’ll use a todos boilerplate to demonstrate this.

Below is the project structure.

\-

--vanilla-nodejs-rest-api

| app.js

| controller.js

| data.js

| utils.js

No sub-folders exist

Adding test data

data.js: Holds some temporary test data. The information is kept in a todos array. Every todo has a unique id, a todo title, a short description, and a Boolean value that marks a completed todo.

//data.js

/** Todos List*/

const

todos

=

[

{

id

:

1

,

title

:

"Coding in Javascript"

,

description

:

"Working with functions in JavaScript"

,

completed

:

false

,

},

{

id

:

2

,

title

:

"Cooking Supper"

,

description

:

"Preparing rice and chicken"

,

completed

:

false

,

},

{

id

:

3

,

title

:

"Taking a walk"

,

description

:

"Easy time at the park"

,

completed

:

false

,

},

{

id

:

4

,

title

:

"Watching Netflix"

,

description

:

"Enjoying the new premiered series"

,

completed

:

false

,

},

];

module

.

exports

=

todos

;

Setting up the controllers

controllers.js: this manages the actual functionality and the logic behind each route used in this application. It is made up of the Controller class, which will have the following major HTTP methods:

  • getTodos(): to get and list down all todos as listed in the temporary data.js file.
  • getTodo(): to get and list a single todo by its unique id.
  • createTodo(): to create a new temporary todo.
  • updateTodo(): to update the values of an existing todo.
  • deleteTodo(): to remove a todo from the list.

// controller.js

// Logic behind the functionalities

const

data

=

require

(

"./data"

);

class

Controller

{

// getting all todos

async

getTodos

() {

// return all todos

return

new

Promise((

resolve

,

_

) =>

resolve

(

data

));

}

// getting a single todo

async

getTodo

(

id

) {

return

new

Promise((

resolve

,

reject

) => {

// get the todo

let

todo

=

data

.

find

((

todo

) =>

todo

.

id

===

parseInt(

id

));

if

(

todo

) {

// return the todo

resolve

(

todo

);

}

else

{

// return an error

reject

(

`Todo with id

${

id

}

not found `

);

}

});

}

// creating a todo

async

createTodo

(

todo

) {

return

new

Promise((

resolve

,

_

) => {

// create a todo, with random id and data sent

let

newTodo

=

{

id

:

Math.

floor

(

4

+

Math.

random

()

*

10

),

...

todo

,

};

// return the new created todo

resolve

(

newTodo

);

});

}

// updating a todo

async

updateTodo

(

id

) {

return

new

Promise((

resolve

,

reject

) => {

// get the todo.

let

todo

=

data

.

find

((

todo

) =>

todo

.

id

===

parseInt(

id

));

// if no todo, return an error

if

(

!

todo

) {

reject

(

`No todo with id

${

id

}

found`

);

}

//else, update it by setting completed to true

todo

[

"completed"

]

=

true

;

// return the updated todo

resolve

(

todo

);

});

}

// deleting a todo

async

deleteTodo

(

id

) {

return

new

Promise((

resolve

,

reject

) => {

// get the todo

let

todo

=

data

.

find

((

todo

) =>

todo

.

id

===

parseInt(

id

));

// if no todo, return an error

if

(

!

todo

) {

reject

(

`No todo with id

${

id

}

found`

);

}

// else, return a success message

resolve

(

`Todo deleted successfully`

);

});

}

}

module

.

exports

=

Controller

;

Utility settings

utils.js: Controls a standard Web API use case. It includes the getReqData() function, which retrieves data from the client on the server.

//utils.js

function

getReqData

(

req

) {

return

new

Promise((

resolve

,

reject

) => {

try

{

let

body

=

""

;

// listen to data sent by client

req

.

on

(

"data"

, (

chunk

) => {

// append the string version to the body

body

+=

chunk

.

toString

();

});

// listen till the end

req

.

on

(

"end"

, () => {

// send back the data

resolve

(

body

);

});

}

catch

(

error

) {

reject

(

error

);

}

});

}

module

.

exports

=

{

getReqData

};

Setting the server and routes

app.js: This contains;

  • Initialization and configuration of the server.
  • The appropriate routes listening to the varying HTTP methods of a server.
  • A PORT number to listen and set the server live on a browser.

//app.js

const

http

=

require

(

"http"

);

const

Todo

=

require

(

"./controller"

);

const

{

getReqData

}

=

require

(

"./utils"

);

const

PORT

=

process

.

env

.

PORT

||

5000

;

const

server

=

http

.

createServer

(

async

(

req

,

res

) => {

// /api/todos : GET

if

(

req

.

url

===

"/api/todos"

&&

req

.

method

===

"GET"

) {

// get the todos.

const

todos

=

await

new

Todo

().

getTodos

();

// set the status code, and content-type

res

.

writeHead

(

200

, {

"Content-Type"

:

"application/json"

});

// send the data

res

.

end

(

JSON

.

stringify

(

todos

));

}

// /api/todos/:id : GET

else

if

(

req

.

url

.

match

(

/\/api\/todos\/([0-9]+)/

)

&&

req

.

method

===

"GET"

) {

try

{

// get id from url

const

id

=

req

.

url

.

split

(

"/"

)[

3

];

// get todo

const

todo

=

await

new

Todo

().

getTodo

(

id

);

// set the status code and content-type

res

.

writeHead

(

200

, {

"Content-Type"

:

"application/json"

});

// send the data

res

.

end

(

JSON

.

stringify

(

todo

));

}

catch

(

error

) {

// set the status code and content-type

res

.

writeHead

(

404

, {

"Content-Type"

:

"application/json"

});

// send the error

res

.

end

(

JSON

.

stringify

({

message

:

error

}));

}

}

// /api/todos/:id : DELETE

else

if

(

req

.

url

.

match

(

/\/api\/todos\/([0-9]+)/

)

&&

req

.

method

===

"DELETE"

) {

try

{

// get the id from url

const

id

=

req

.

url

.

split

(

"/"

)[

3

];

// delete todo

let

message

=

await

new

Todo

().

deleteTodo

(

id

);

// set the status code and content-type

res

.

writeHead

(

200

, {

"Content-Type"

:

"application/json"

});

// send the message

res

.

end

(

JSON

.

stringify

({

message

}));

}

catch

(

error

) {

// set the status code and content-type

res

.

writeHead

(

404

, {

"Content-Type"

:

"application/json"

});

// send the error

res

.

end

(

JSON

.

stringify

({

message

:

error

}));

}

}

// /api/todos/:id : UPDATE

else

if

(

req

.

url

.

match

(

/\/api\/todos\/([0-9]+)/

)

&&

req

.

method

===

"PATCH"

) {

try

{

// get the id from the url

const

id

=

req

.

url

.

split

(

"/"

)[

3

];

// update todo

let

updated_todo

=

await

new

Todo

().

updateTodo

(

id

);

// set the status code and content-type

res

.

writeHead

(

200

, {

"Content-Type"

:

"application/json"

});

// send the message

res

.

end

(

JSON

.

stringify

(

updated_todo

));

}

catch

(

error

) {

// set the status code and content type

res

.

writeHead

(

404

, {

"Content-Type"

:

"application/json"

});

// send the error

res

.

end

(

JSON

.

stringify

({

message

:

error

}));

}

}

// /api/todos/ : POST

else

if

(

req

.

url

===

"/api/todos"

&&

req

.

method

===

"POST"

) {

// get the data sent along

let

todo_data

=

await

getReqData

(

req

);

// create the todo

let

todo

=

await

new

Todo

().

createTodo

(

JSON

.

parse

(

todo_data

));

// set the status code and content-type

res

.

writeHead

(

200

, {

"Content-Type"

:

"application/json"

});

//send the todo

res

.

end

(

JSON

.

stringify

(

todo

));

}

// No route present

else

{

res

.

writeHead

(

404

, {

"Content-Type"

:

"application/json"

});

res

.

end

(

JSON

.

stringify

({

message

:

"Route not found"

}));

}

});

server

.

listen

(

PORT

, () => {

console

.

log

(

`server started on port:

${

PORT

}

`

);

});

Testing the app

The Vanilla Node.js REST API is now set and ready to test to see if everything is working. Now you need to start the server by running the following command:

node app.js

This will set and run the server on port 5000.

A simple server api

Explore the API using Postman

Let’s test the different methods set in the API using Postman. If you are new to Postman API testing, please go over this tutorial to get started.

Fetch all todos

/API/todos GET: This will fetch all the todos listed in the data.js.

To test this GET request:

  • Go over to Postman and SEND a GET request. The request URL as http://localhost:5000/api/todos as shown below:

Get all todos

  • This will log a response in the Postman response section, with all the todos as listed in data.js

Get all todos

Fetch a todo by id

/API/todos/:id GET: This will fetch only one todo specified with the value of the todo’s id.

To test this GET request:

  • Go over to Postman and SEND a GET request. Enter the request URL as http://localhost:5000/api/todos/:id, where :id is the id of the single todo you want to fetch, as shown below.

Get a single todo

  • The request will log a single todo to the Postman response section.

Get a single todo

DELETE a todo

/API/todos/:id DELETE: This will perform a DELETE request of a single todo. You will just receive a response message because the data is temporary and not stored in a database.

To put it to the test:

  • Go over to Postman and SEND a DELETE request. Enter the request URL as http://localhost:5000/api/todos/:id, where :id is the id of the single todo you want to delete, as shown below.

Delete a todo

  • This will log a Todo deleted successfully message in your Postman response console.

Delete a todo

/API/todos/:id PATCH: This will update a todo to indicate that the task is completed, i.e., true or false. You will see the effect on the postman response console.

To see how well it works:

  • Go over to Postman and SEND a PATCH request. Enter the request URL as http://localhost:5000/api/todos/:id, where :id is the id of the single todo you want to update, as shown below.

Update a todo

  • The Postman response console should look like this:

Update a todo

ADD a new todo

/API/todos POST: This will create a new todo item. The new todo will be returned as a response, but it will not be recorded in the data.js.

To put it to the test, do the following:

  • Go over to Postman and open a new tab, select a POST request and enter the request URL as http://localhost:5000/api/todos.

Add a new todo

Go over to the Body tab section, select raw, and select’ JSON’ from the dropdown options to the right.

Add a new todo

  • Add the new todo’s contents (title, the description, and completed).

Here is a simple illustration:

{

"title"

:

"Vannila Node.js REST API"

,

"description"

:

"Working with responses and requests"

,

"completed"

:

false

}

  • After filling the above details, hit the SEND button to start the POST request, and the newly added todo will be logged in the Postman console.

Add a new todo

Note: Because the id is generated randomly, it may vary every time you make a new POST request.

There you have it: a straightforward REST API written entirely in Vannila Node.js. I hope you have found this tutorial instructive, informative, and helpful.

Happy coding!!!

Peer Review Contributions by: Peter Kayere