Introduction#

GitHub this note shows

  • Node.js and Typescript
  • Node.js http server
  • Prisma and PostgreSQL

Setup Project#

Let create a new TypeScript project

npm init
npm install --save-dev typescript tslint @types/node

Then create a tsconfig.json

{
"compilerOptions": {
"lib": ["ES2015"],
"module": "CommonJS",
"outDir": "dist",
"sourceMap": true,
"strict": true,
"target": "ES2015"
},
"include": ["src"]
}

Next install Node.js http server

npm i http-server

Then install ESJ template engine

npm i ejs

Project structure

|--node_modules
|--prisma
|--schema.presima
|--src
|--index.ts
|--test.ts
|--static
|--book.html
|--upload.html
|--.env
|--package.json
|--tsconfig.json

HTTP Server#

Let create a simple http server to handler

  • Static html file
  • Response json
  • Response with template ESJ
const handleStatic = (request: IncomingMessage, response: ServerResponse) => {
response.setHeader("Content-Type", "text/html");
response.writeHead(200);
fs.readFile(path.join(__dirname, "./../static/book.html"), (error, data) => {
response.end(data);
});
};
const listener = (req: IncomingMessage, res: ServerResponse) => {
if (req.url == "/book") {
handleStatic(req, res);
}
};
const server = createServer(listener);
server.listen(3000, () => {
console.log("listening...");
});

Full detail example as below

http server example
import { IncomingMessage, ServerResponse, createServer } from 'http'
import * as fs from 'fs'
import * as path from 'path'
import * as formidable from 'formidable'
import * as ejs from 'ejs'
import { PrismaClient } from '@prisma/client'
const prisma = new PrismaClient()
;(BigInt.prototype as any).toJSON = function () {
return this.toString()
}
const handleStatic = (request: IncomingMessage, response: ServerResponse) => {
response.setHeader('Content-Type', 'text/html')
response.writeHead(200)
fs.readFile(path.join(__dirname, './../static/book.html'), (error, data) => {
response.end(data)
})
}
const handleUpload = async (req: IncomingMessage, res: ServerResponse) => {
if (req.url == '/fileupload') {
console.log('file uploading ...')
const form = new formidable.IncomingForm({})
let fields
let files
try {
;[fields, files] = await form.parse(req)
console.log(files)
files.myFile?.forEach(file => {
console.log(file.originalFilename)
fs.copyFile(
file.filepath,
path.join(__dirname, './../static/hehe.jpg'),
error => {
console.log(error)
}
)
// fs.rename(
// file.filepath,
// path.join(__dirname, "./../static/hehe.jpg"),
// (error) => {
// console.log(error);
// }
// );
})
} catch (error) {}
res.writeHead(200, { 'Content-Type': 'application/json' })
res.end(JSON.stringify({ fields, files }, null, 2))
} else {
fs.readFile(
path.join(__dirname, './../static/upload.html'),
(error, data) => {
console.log('read upload form ...', data)
res.writeHead(200, { 'Content-Type': 'text/html' })
res.write(data)
res.end()
}
)
}
}
const handleDownloadBooks = async (
req: IncomingMessage,
res: ServerResponse
) => {
try {
const books = await prisma.book.findMany()
console.log(books)
// res.writeHead(200, { "Content-Type": "application/json" });
// res.end(JSON.stringify({ books }, null, 2));
// const content = ejs.render("<%= username %>", { username: "minh hai" });
const content = ejs.render(
`
<% for (var i = 0; i < books.length; i++) { %>
<div class='card'>
<h4 class='title'><%= books[i].author %></h4>
<h4 class='title'><%= books[i].title%></h4>
<img src='https://d2cvlmmg8c0xrp.cloudfront.net/web-css/data_engineering_with_aws.jpg' class='image'/>
<p>
Lorem ipsum, dolor sit amet consectetur adipisicing elit. Officia officiis voluptates ab eum totam atque deleniti accusantium nulla illo provident et nesciunt, nisi laudantium iusto animi rem repudiandae, asperiores consequuntur Lorem ipsum dolor sit amet, consectetur adipisicing elit. Doloremque ipsam deserunt quaerat corrupti nihil error amet libero. Dignissimos, dolorem laudantium optio id, blanditiis eveniet repellendus pariatur neque facilis reprehenderit excepturi! Lorem ipsum dolor sit amet consectetur, adipisicing elit. Non repellendus, praesentium quasi quidem itaque numquam qui ex ducimus harum, perferendis officia deserunt libero magni assumenda mollitia aut ratione ipsam illo! Lorem ipsum dolor sit amet consectetur adipisicing elit. Maxime, corporis suscipit, natus odio nobis vel totam atque vitae porro animi in, cupiditate mollitia pariatur minus quos! Maiores assumenda explicabo expedita?
</p>
</div>
<% } %>
`,
{ books: books }
)
res.writeHead(200, { 'Content-Type': 'text/html' })
res.write(
`
<html>
<head>
<style>
:root {
box-sizing: border-box;
}
*,
::before,
::after {
box-sizing: inherit;
}
.body {
background-color: antiquewhite;
}
.container {
max-width: 800px;
margin-left: auto;
margin-right: auto;
}
.grid {
display: grid;
row-gap: 10px;
column-gap: 10px;
grid-template-columns: repeat(1, minmax(0, 1fr));
}
.card {
margin-left: 4px;
margin-right: 4px;
padding: 0.5em;
background-color: white;
width: 100%;
}
@media (min-width: 800px) {
.grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
.image {
float: left;
height: auto;
width: 128px;
margin-right: 6px;
}
.title {
font: bold;
margin-bottom: 8px;
}
</style>
</head>
<body class='body'>
<div class='container'>
<div class='grid'>
${content}
</div>
</div>
</body>
</html>
`
)
res.end()
} catch (error) {
console.log(error)
res.writeHead(200, { 'Content-Type': 'application/json' })
res.end('ERROR')
}
}
const listener = (req: IncomingMessage, res: ServerResponse) => {
if (req.url == '/book') {
handleStatic(req, res)
}
if (req.url == '/' || req.url == '/fileupload') {
handleUpload(req, res)
}
if (req.url == '/prisma') {
handleDownloadBooks(req, res)
}
}
const server = createServer(listener)
server.listen(3000, () => {
console.log('listening...')
})

Prisma and PostgreSQL#

  • Setup prisma
  • Get books
  • Response using ESJ template

Let setup prisma for our typescript project

npm install prisma --save-dev

Then invoke Prisma CLI by prefixing it with npx

npx prisma

Let init Prisma project which will create a director called prisma with schema.prisma and an .env

npx prisma init

Let update the .env with database connection, for example, connect to a postgresql running on my local machine

DATABASE_URL="postgresql://demo:Mike@865525@localhost:5432/dvdrental?schema=public"

Assume that there are already some tables in the dvdrental database, now query it using prisma. Let pull database

npx prisma db pull

Then the schema.prisma is updated as the following

generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model actor {
actor_id Int @id @default(autoincrement())
first_name String @db.VarChar(45)
last_name String @db.VarChar(45)
last_update DateTime @default(now()) @db.Timestamp(6)
@@index([last_name], map: "idx_actor_last_name")
}
/// We could not retrieve columns for the underlying table. Either it has none or you are missing rights to see them. Please check your privileges.
// model address {
// }
model book {
id BigInt @id @default(autoincrement())
title String?
author String?
amazon String?
image String?
description String?
}

Now we need to install Prisma client

npm install @prisma/client

And generate client code

npx prisma generate

Finally we can write code to get books from the book table

import { PrismaClient } from '@prisma/client'
const prisma = new PrismaClient()
;(BigInt.prototype as any).toJSON = function () {
return this.toString()
}
const getBooks = async () => {
try {
const books = await prisma.book.findMany()
console.log(books)
console.log(JSON.stringify({ books }), null, 2)
} catch (error) {
console.log(error)
}
}
getBooks()

Here is the full handler for getbooks from database and response to users.

handleDownloadBooks
const handleDownloadBooks = async (
req: IncomingMessage,
res: ServerResponse
) => {
try {
const books = await prisma.book.findMany()
console.log(books)
// res.writeHead(200, { "Content-Type": "application/json" });
// res.end(JSON.stringify({ books }, null, 2));
// const content = ejs.render("<%= username %>", { username: "minh hai" });
const content = ejs.render(
`
<% for (var i = 0; i < books.length; i++) { %>
<div class='card'>
<h4 class='title'><%= books[i].author %></h4>
<h4 class='title'><%= books[i].title%></h4>
<img src='https://d2cvlmmg8c0xrp.cloudfront.net/web-css/data_engineering_with_aws.jpg' class='image'/>
<p>
Lorem ipsum, dolor sit amet consectetur adipisicing elit. Officia officiis voluptates ab eum totam atque deleniti accusantium nulla illo provident et nesciunt, nisi laudantium iusto animi rem repudiandae, asperiores consequuntur Lorem ipsum dolor sit amet, consectetur adipisicing elit. Doloremque ipsam deserunt quaerat corrupti nihil error amet libero. Dignissimos, dolorem laudantium optio id, blanditiis eveniet repellendus pariatur neque facilis reprehenderit excepturi! Lorem ipsum dolor sit amet consectetur, adipisicing elit. Non repellendus, praesentium quasi quidem itaque numquam qui ex ducimus harum, perferendis officia deserunt libero magni assumenda mollitia aut ratione ipsam illo! Lorem ipsum dolor sit amet consectetur adipisicing elit. Maxime, corporis suscipit, natus odio nobis vel totam atque vitae porro animi in, cupiditate mollitia pariatur minus quos! Maiores assumenda explicabo expedita?
</p>
</div>
<% } %>
`,
{ books: books }
)
res.writeHead(200, { 'Content-Type': 'text/html' })
res.write(
`
<html>
<head>
<style>
:root {
box-sizing: border-box;
}
*,
::before,
::after {
box-sizing: inherit;
}
.body {
background-color: antiquewhite;
}
.container {
max-width: 800px;
margin-left: auto;
margin-right: auto;
}
.grid {
display: grid;
row-gap: 10px;
column-gap: 10px;
grid-template-columns: repeat(1, minmax(0, 1fr));
}
.card {
margin-left: 4px;
margin-right: 4px;
padding: 0.5em;
background-color: white;
width: 100%;
}
@media (min-width: 800px) {
.grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
.image {
float: left;
height: auto;
width: 128px;
margin-right: 6px;
}
.title {
font: bold;
margin-bottom: 8px;
}
</style>
</head>
<body class='body'>
<div class='container'>
<div class='grid'>
${content}
</div>
</div>
</body>
</html>
`
)
res.end()
} catch (error) {
console.log(error)
res.writeHead(200, { 'Content-Type': 'application/json' })
res.end('ERROR')
}
}

Reference#