Learn how to create a simple HTTP server using TypeScript and Node.js, handle requests, send responses, and explore basic routing and static content serving.
In this section, we’ll explore how to build a simple HTTP server using TypeScript and Node.js. We’ll cover the basics of handling requests, sending responses, and introduce routing and serving static content. By the end of this guide, you’ll have a solid foundation for creating more complex web servers.
Before we dive into creating an HTTP server, let’s ensure that your development environment is ready. You’ll need Node.js and TypeScript installed on your machine. If you haven’t set them up yet, refer to the earlier section on setting up your development environment.
Node.js provides a built-in module called http
that allows us to create an HTTP server. Let’s start by creating a basic server that listens for incoming requests and sends a simple response.
First, create a new directory for your project and navigate into it:
mkdir my-http-server
cd my-http-server
Initialize a new Node.js project:
npm init -y
This command creates a package.json
file with default settings.
Next, install TypeScript as a development dependency:
npm install typescript --save-dev
Create a tsconfig.json
file to configure the TypeScript compiler:
{
"compilerOptions": {
"target": "ES6",
"module": "commonjs",
"outDir": "./dist",
"rootDir": "./src",
"strict": true
},
"include": ["src"]
}
Create a new directory named src
and add a file called server.ts
inside it:
mkdir src
touch src/server.ts
Open server.ts
in your code editor and add the following code:
import * as http from 'http';
// Create an HTTP server
const server = http.createServer((req, res) => {
res.statusCode = 200; // Set the status code to 200 (OK)
res.setHeader('Content-Type', 'text/plain'); // Set the response content type
res.end('Hello, World!\n'); // Send a response
});
// Define the port number
const port = 3000;
// Start the server and listen on the specified port
server.listen(port, () => {
console.log(`Server running at http://localhost:${port}/`);
});
Compile the TypeScript code to JavaScript using the TypeScript compiler:
npx tsc
This command generates a dist
directory containing the compiled JavaScript files. Now, run the server:
node dist/server.js
Open your web browser and navigate to http://localhost:3000/
. You should see “Hello, World!” displayed on the page.
Now that we have a basic server running, let’s explore how to handle different types of requests and send appropriate responses.
In Node.js, the http
module provides two important objects: IncomingMessage
and ServerResponse
. These objects represent the request and response, respectively.
Let’s modify our server to handle different HTTP request methods, such as GET and POST.
import * as http from 'http';
const server = http.createServer((req, res) => {
const { method, url } = req;
if (method === 'GET' && url === '/') {
res.statusCode = 200;
res.setHeader('Content-Type', 'text/plain');
res.end('Welcome to the homepage!\n');
} else if (method === 'POST' && url === '/data') {
let body = '';
// Collect data from the request body
req.on('data', chunk => {
body += chunk.toString();
});
req.on('end', () => {
res.statusCode = 200;
res.setHeader('Content-Type', 'application/json');
res.end(`Received data: ${body}\n`);
});
} else {
res.statusCode = 404;
res.setHeader('Content-Type', 'text/plain');
res.end('Not Found\n');
}
});
const port = 3000;
server.listen(port, () => {
console.log(`Server running at http://localhost:${port}/`);
});
In this example, we handle GET requests to the root URL (/
) and POST requests to /data
. For other routes, we return a 404 Not Found status.
Routing is the process of determining how an application responds to a client request to a particular endpoint. Let’s implement basic routing and serve static content.
We can extend our server to handle more routes by checking the url
and method
properties of the request object.
import * as http from 'http';
const server = http.createServer((req, res) => {
const { method, url } = req;
switch (url) {
case '/':
if (method === 'GET') {
res.statusCode = 200;
res.setHeader('Content-Type', 'text/plain');
res.end('Welcome to the homepage!\n');
}
break;
case '/about':
if (method === 'GET') {
res.statusCode = 200;
res.setHeader('Content-Type', 'text/plain');
res.end('About us page\n');
}
break;
default:
res.statusCode = 404;
res.setHeader('Content-Type', 'text/plain');
res.end('Not Found\n');
}
});
const port = 3000;
server.listen(port, () => {
console.log(`Server running at http://localhost:${port}/`);
});
To serve static files, such as HTML, CSS, or images, we can use the fs
module to read files from the file system and send them as responses.
import * as http from 'http';
import * as fs from 'fs';
import * as path from 'path';
const server = http.createServer((req, res) => {
const { method, url } = req;
if (method === 'GET' && url === '/') {
const filePath = path.join(__dirname, 'public', 'index.html');
fs.readFile(filePath, (err, data) => {
if (err) {
res.statusCode = 500;
res.setHeader('Content-Type', 'text/plain');
res.end('Internal Server Error\n');
} else {
res.statusCode = 200;
res.setHeader('Content-Type', 'text/html');
res.end(data);
}
});
} else {
res.statusCode = 404;
res.setHeader('Content-Type', 'text/plain');
res.end('Not Found\n');
}
});
const port = 3000;
server.listen(port, () => {
console.log(`Server running at http://localhost:${port}/`);
});
In this example, we serve an index.html
file located in a public
directory when the root URL is accessed.
TypeScript allows us to add type annotations to our code, making it easier to catch errors and understand the code’s structure. Let’s add types to our server code.
import * as http from 'http';
import * as fs from 'fs';
import * as path from 'path';
const server: http.Server = http.createServer((req: http.IncomingMessage, res: http.ServerResponse) => {
const { method, url } = req;
if (method === 'GET' && url === '/') {
const filePath: string = path.join(__dirname, 'public', 'index.html');
fs.readFile(filePath, (err: NodeJS.ErrnoException | null, data: Buffer) => {
if (err) {
res.statusCode = 500;
res.setHeader('Content-Type', 'text/plain');
res.end('Internal Server Error\n');
} else {
res.statusCode = 200;
res.setHeader('Content-Type', 'text/html');
res.end(data);
}
});
} else {
res.statusCode = 404;
res.setHeader('Content-Type', 'text/plain');
res.end('Not Found\n');
}
});
const port: number = 3000;
server.listen(port, () => {
console.log(`Server running at http://localhost:${port}/`);
});
By adding type annotations, we improve code readability and leverage TypeScript’s type-checking capabilities.
Now that you have a basic understanding of creating an HTTP server, try experimenting with adding more routes or implementing middleware functions.
Add more routes to your server by checking the url
and method
properties. For example, you can create a route for /contact
that returns contact information.
Middleware functions are functions that have access to the request and response objects and can modify them or end the request-response cycle. You can create middleware to log requests, authenticate users, or handle errors.
/contact
that returns contact information.public
directory and serve it along with the HTML file.Let’s visualize the flow of an HTTP server using a Mermaid.js diagram:
graph TD; A[Client Request] --> B[HTTP Server]; B --> C{Route Matching}; C -->|Match Found| D[Handle Request]; C -->|No Match| E[404 Not Found]; D --> F[Send Response]; E --> F; F --> G[Client Receives Response];
This diagram illustrates the process of a client request being received by the server, matched to a route, handled, and then a response being sent back to the client.
In this section, we learned how to create a simple HTTP server using TypeScript and Node.js. We explored handling requests, sending responses, implementing basic routing, and serving static content. We also discussed typing request and response objects and encouraged experimentation with routes and middleware.