HTTP image server without any npm package
Last updated on March 8, 2022. Created on March 7, 2022.
Table of Contents
Problem
- We have an image
- We want to build a server to share this file online
- How can do that in NodeJS without any npm package
Pre-requisite
- An intermediate level understanding of NodeJS and Javascript
- You have to be familiar with the idea of streams. click here to learn about NodeJS streams
- Understand HTTP protocol. click here to learn more about the HTTP protocol
Folder structure
Our project should contain two files.
- index.js : this should house our application logic
- images/file.png: the file we want to serve should be named
file.png
and placed in images folder
images/:
file.png
index.js
💡 The code is a gradual build up to the final working code, you can keep track of the curent section by paying attention to the comments within the code
Create an HTTP server to listen for incoming request
Ok, we’d start by creating an http server. NodeJS has a build-in http
module with methods for handling all things HTTP related, like in our case we would need the createServer
method. The http.createServer
method returns a *http.Server
*object , we would store that in a variable server
.
const http = require('http')
//create server
const server = http.createServer()
Start server
The server
contains a listen
method that we would call to start listening for incoming requests. We would pass the port we want our server to listen on to the listen
method
In our case we would choose port 5000
const http = require('http')
const server = http.createServer()
//start server
server.listen(5000)
Handle listening event.
Its often handy to log when your server has started listening, otherwise we might not be sure everything works as planned.
You might also want to do something else when your server starts listening.
Thankfully the server
object is an event emitter, and we can listen for the listening event.
const http = require('http')
const server = http.createServer()
server.listen(5000)
//handle server listening
server.on('listening',()=>console.log('listening on 5000'))
Handle incoming requests
The http.createServer
method accepts a callback function as parameter. The callback function we provide would be called whenever there is a new request.
The callback function would be called with two arguments.
- IncomingRequest object : this object contains information about the incoming request
- ServerReponse Object : this object contains properties and methods to structure a response.
In this case our callback would accept the IncomingRequest as req
and the ServerReponse as res
const http = require('http')
const server = http.createServer((req,res)=>{
//handle incoming requests
})
server.listen(5000)
server.on('listening',()=>console.log('listening on 5000'))
Send Our Image as Response
To send our response to an incoming request, we need to first check if the request is asking for the information.
We would inspect the request url to see if it matches our determined route.
We would chose the "/"
as our route. We can choose any other route like "/image"
relative to our host address. But in this case we would go with "/"
for simplicity.
That is to say, assuming our host is localhost:5000
. if a client visits http://localhost:5000
or http://localhost:5000/
we should respond with our image.
Let’s go through steps of how we would send our response if the url matches “/“
- set our content type to
"image/png"
by calling theres.setHeader
method . Which takes in two parameters; header which in this case is"Content-Type"
and value which we would like to be"image/png"
. This would help our client know what type of file we are sending. - Create a Readable stream from our file by passing our file path to
fs.createReadSteam
method. - call the
pipe
method of our file readable stream and pass theres
object which is a writable stream as an argument. this should handle streaming the file to our client. - Although not necessary, but we can listen for when we have finished streaming a file by listening to the end event of our file stream.
const http = require('http')
const fs = require('fs')
const server = http.createServer((req,res)=>{
//send image as response
if (req.url=="/"){
res.setHeader('Content-Type','image/png')
const file = fs.createReadStream('images/file.png')
file.pipe(res)
file.on('end',()=>console.log('image sent',new Date()))
}
})
server.listen(5000)
server.on('listening',()=>console.log('listening on 5000'))
Handle 404 (not found)
404 is the status code our server should respond with when a route a client requests for is not found, according to the HTTP protocol specification.
Since this is a single image server, we would need Just one route to work, any other route should have the 404 response.
Our chosen url to serve our image is the "/"
route.
That is to say, assuming our host is localhost:5000
. if a client visits any route that is not http://localhost:5000
or http://localhost:5000/
. We should respond with a 404 error response.
We would handle the 404 error buy doing 3 things
- set our status code to 404 by assigning 404 to the
res.statusCode
property - set our content type to
"text/plain"
by calling theres.setHeader
method . Which takes in two parameters; header which in this case is"Content-Type"
and value which we would like to be"text/plain"
- send the actual response by calling the
res.end
method and passing it our response body which is just a plain text"Not found!"
const http = require('http')
const fs = require('fs')
const server = http.createServer((req,res)=>{
if (req.url=="/"){
res.setHeader('Content-Type','image/png')
const file = fs.createReadStream('images/file.png')
file.pipe(res)
file.on('end',()=>console.log('image sent',new Date()))
}
else{
//Handle 404 error
console.error(req.url,'not found')
res.statusCode = 404
res.setHeader('Content-Type','text/plain')
res.end('Not Found!')
}
})
server.listen(5000)
server.on('listening',()=>console.log('listening on 5000'))
If you have trouble following this up, you most likely need to study up on http protocol
Handle Transfer Error
As we know. Sometimes things don’t go as expected. We don’t want the client waiting too long. We want to update the client if something goes wrong with his Download. To handle Transfer errors , we would listen to the error event on our file stream, and provide a callback function that would be expecting the error object.
We don’t want to send anything from the error object directly to the client. This might leak some information about how our server works to the client, and we don’t trust clients. Clients might be hackers.
We would construct a nice 500 error for the client to notify them that something went wrong on the server.
To send the 500 Error, We would:
- set our status code to 500 by assigning 500 to the
res.statusCode
property - set our content type to
"text/plain"
by calling theres.setHeader
method . Which takes in two parameters; header which in this case is"Content-Type"
and value which we would like to be"text/plain"
- send the actual response by calling the
res.end
method and passing it our response body which is just a plain text"something went wrong"
const http = require('http')
const fs = require('fs')
const server = http.createServer((req,res)=>{
if (req.url=="/"){
res.setHeader('Content-Type','image/png')
const file = fs.createReadStream('images/file.png')
file.pipe(res)
file.on('end',()=>console.log('image sent',new Date()))
//handle tranfer error
file.on('error',(error)=>{
console.error(error)
res.statusCode = 500
res.setHeader('Content-Type','text/plain')
res.end('something went wrong')
})
}
else{
console.error(req.url,'not found')
res.statusCode = 404
res.setHeader('Content-Type','text/plain')
res.end('Not Found!')
}
})
server.listen(5000)
server.on('listening',()=>console.log('listening on 5000'))
How to run the code
Run node index.js
command within the application directory to run the code.
Live demo
You can run the live demo on replit
Conclusion
So I hope this answers the question of how to build a server with any npm library.
I would still advice you use express or any other web server framework. as they will handles more issues that you won’t even think of and simplify your code alot. But this knowledge helps you know how to extend these libraries if you want to.
See you later. Bye 👋🏾