this course base on this template https://github.com/billowdev/nodejs-ts-fastify-mvc-starter-template
npm init -y
npm install -g yarn
yarn add
yarn add -D
yarn
npm install --save-dev ts-node @types/node typescript
or
yarn add -D ts-node @types/node typescript
npx tsc --init
{
"compilerOptions": {
/* Visit https://aka.ms/tsconfig to read more about this file */
/* Language and Environment */
"target": "ESNext", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
/* Modules */
"module": "commonjs", /* Specify what module code is generated. */
// "rootDir": "./", /* Specify the root folder within your source files. */
"moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
"baseUrl": "./src", /* Specify the base directory to resolve non-relative module names. */
"outDir": "./dist", /* Specify an output folder for all emitted files. */
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
/* Type Checking */
"strict": true, /* Enable all strict type-checking options. */
"skipLibCheck": true /* Skip type checking all .d.ts files. */
},
"include": [
"**/*.ts"
, "src/config/db.config.ts" ],
"exclude": [
"node_modules",
"**/*.d.ts",
"**/*.spec.ts"
]
}
npm install fastify
or
yarn add fastify
index.ts
import fastify from "fastify";
const app = fastify()
app.get("/", async () => "SERVER");
const PORT = 5000
app.listen({port:Number(PORT)}, (err) => {
if (err) {
console.error(err)
process.exit(1)
}
console.log(`SERVE ON ${PORT}`)
})
ts-node index.ts
const app = fastify({
logger: true
})
const PORT = 5000
app.listen({port:Number(PORT)}, (err) => {
if (err) {
app.log.error(err);
process.exit(1)
}
app.log.info(`SERVE ON ${PORT}`)
})
{"level":30,"time":1676779096246,"pid":25344,"hostname":"billo","msg":"Server listening at http://[::1]:5000"}
{"level":30,"time":1676779096248,"pid":25344,"hostname":"billo","msg":"Server listening at http://127.0.0.1:5000"}
{"level":30,"time":1676779096248,"pid":25344,"hostname":"billo","msg":"SERVE ON 5000"}
however you can use another solution for test your api like postman or if api you is get method you also use browser for that but i prefer using thunder client or postman or insomnia because it can use another http method such as GET POST PUT PATCH DELETE etc.
import fastify from "fastify";
const app = fastify({
logger: true
})
app.get("/", async () => "SERVER");
const PORT = 5000
app.listen({port:Number(PORT)}, (err) => {
if (err) {
app.log.error(err);
process.exit(1)
}
app.log.info(`SERVE ON ${PORT}`)
})
import fastify, { FastifyServerOptions } from "fastify";
const App = (options: FastifyServerOptions) => {
const app = fastify(options)
app.get("/", async () => "SERVER");
return app
}
export default App
import App from "./src/app";
const app = App({
logger: true
})
const PORT = 5000
app.listen({port:Number(PORT)}, (err) => {
if (err) {
app.log.error(err);
process.exit(1)
}
app.log.info(`SERVE ON ${PORT}`)
})
article.route.ts
import { FastifyInstance } from "fastify"; // import FastifyInstance
const articleRouter = async (app: FastifyInstance) => {
// route api app.method("path", {option}, handler)
// create .get route endpoint for article route that '/'
// mockup data
const article = {
id: "1",
name: "node.js fastify",
desc: "going fasting with jumping course 0 to 100 ><"
}
app.get(
"/",
// function handler: RouteHandlerMethod<RawServerDefau lt, IncomingMessage, ServerResponse<IncomingMessage>, RouteGenericInterface, unknown, FastifySchema, FastifyTypeProviderDefault, FastifyBaseLogger>):
() => {
return {
// mockup data
articles: [
article
]
}
}
);
};
export default articleRouter;
import articleRouter from "./article.route";
export { articleRouter};
import fastify, { FastifyServerOptions } from "fastify";
import {articleRouter} from "./routes";
const App = (options: FastifyServerOptions) => {
const app = fastify(options)
app.get("/", async () => "SERVER");
app.register(articleRouter, { prefix: "/api/v1/articles" });
return app
}
export default App
export const handleGetArticle = () => {
// mockup data
const article = {
id: "1",
name: "node.js fastify",
desc: "going fasting with jumping course 0 to 100 ><"
}
return {
// mockup data
articles: [
article
]
}
}
export default {
handleGetArticle
}
import articlesController from "./article.controller";
export { articlesController };
import { FastifyInstance } from "fastify"; // import FastifyInstance
import articleController from './../controllers/article.controller';
const articleRouter = async (app: FastifyInstance) => {
// route api app.method("path", {option}, handler)
app.get(
"/",
// function handler
articleController.handleGetArticle
);
};
export default articleRouter;
export const getArticles = () => {
const data = {
id: "1",
name: "node.js fastify",
desc: "going fasting with jumping course 0 to 100 ><"
}
return { response: data }
}
export default {
getArticles
}
import articleService from "./article.service";
export { articleService };
import { articleService } from "../services";
export const handleGetArticle = () => {
return articleService.getArticles()
}
export default {
handleGetArticle
}
1. install laragon following this -> Youtube
- for sequelize is orm using to interact with database that base on model that our declare
- for dotenv is package for using environment or .env file that our put variable to keep the secret thing like a username password of database
- for mysql2 is require when our use MySQL if using postgres will be pg and pg-hstore etc. for another database should back to document of that software
npm install sequelize dotenv mysql2
or
yarn add sequelize dotenv mysql2
the username of mysql laragon by default that is root and password="" i seperate that env for config database in difference env -> dev, prod, test
NODE_ENV=development
DB_USERNAME_DEV=root
DB_PASSWORD_DEV=""
DB_DATABASE_DEV=node_fastify_db
DB_HOST_DEV=localhost
DB_USERNAME_PROD=root
DB_PASSWORD_PROD=""
DB_DATABASE_PROD=node_fastify_db_production
DB_HOST_PROD=localhost
DB_USERNAME_TEST=root
DB_PASSWORD_TEST=""
DB_DATABASE_TEST=node_fastify_db_test
DB_HOST_TEST=localhost
DB_DIALECT=mysql
the first of all we will import dotenv for using .env file then we create config for create config variable that get value from .env file
import dotenv from "dotenv";
dotenv.config();
const config = {
env: process.env.NODE_ENV || "development",
port: process.env.PORT || 5000,
database: {
dev: {
username: process.env.DB_USERNAME_DEV,
password: process.env.DB_PASSWORD_DEV,
name: process.env.DB_DATABASE_DEV,
host: process.env.DB_HOST_DEV,
},
production:{
username: process.env.DB_USERNAME_PROD,
password: process.env.DB_PASSWORD_PROD,
name: process.env.DB_DATABASE_PROD,
host: process.env.DB_HOST_PROD,
},
test: {
username: process.env.DB_USERNAME_TEST,
password: process.env.DB_PASSWORD_TEST,
name: process.env.DB_DATABASE_TEST,
host: process.env.DB_HOST_TEST,
},
dialect: process.env.DB_DIALECT,
},
};
export default config;
import config from "./config"; // this is important!
module.exports = {
development: {
username: config.database.dev.username,
password: config.database.dev.password,
database: config.database.dev.name,
host: config.database.dev.host,
dialect: config.database.dialect,
},
test: {
username: config.database.test.username,
password: config.database.test.password,
database: config.database.test.name,
host: config.database.test.host,
dialect: config.database.dialect,
},
production: {
username: config.database.production.username,
password: config.database.production.password,
database: config.database.production.name,
host: config.database.production.host,
dialect: config.database.dialect,
},
};
export interface ArticleAttributes {
id?: string;
title?: string;
text?: string;
type?: string;
UserId?: string;
createdAt?: Date;
updatedAt?: Date;
}
code below in commonly that generate by sequelize-cli as models/index.js
but for typescript i modify that get config from "/../config/db.config.ts"
"use strict";
const fs = require("fs");
const path = require("path");
const basename = path.basename(__filename);
const Sequelize = require("sequelize");
const env = process.env.NODE_ENV || "development";
const config = require(__dirname + "/../config/db.config.ts")[env];
const db: any = {};
let sequelize: any;
if (config.use_env_variable) {
sequelize = new Sequelize(process.env[config.use_env_variable], config);
} else {
sequelize = new Sequelize(
config.database,
config.username,
config.password,
config
);
}
fs.readdirSync(__dirname)
.filter((file: string) => {
return (
file.indexOf(".") !== 0 && file !== basename && file.slice(-3) === ".ts"
);
})
.forEach((file: any) => {
const model = require(path.join(__dirname, file))(
sequelize,
Sequelize.DataTypes
);
db[model.name] = model;
});
Object.keys(db).forEach((modelName) => {
if (db[modelName].associate) {
db[modelName].associate(db);
}
});
db.sequelize = sequelize;
db.Sequelize = Sequelize;
export default db;
create the article.model.ts file and then models/index.ts will read this file for instance the model following that our declare such as
"use strict";
import * as Sequelize from "sequelize";
import { Model, UUIDV4 } from "sequelize";
import { ArticleAttributes } from "types/articles/article.model.types";
module.exports = (sequelize: any, DataTypes: any) => {
class Article
extends Model<ArticleAttributes>
implements ArticleAttributes
{
/**
* Helper method for defining associations.
* This method is not a part of Sequelize lifecycle.
* The `models/index` file will call this method automatically.
*/
id!: string;
title!: string;
text!: string;
type!: string;
static associate(models: any) {
// define association here
}
}
Article.init(
{
id: {
type: DataTypes.UUID,
defaultValue: UUIDV4,
allowNull: false,
primaryKey: true,
},
title: {
type: DataTypes.STRING,
allowNull: false,
},
text: {
type: DataTypes.STRING(500),
},
},
{
sequelize,
modelName: "Article",
}
);
return Article;
};
change of file :
- import database and config file
- get PORT From config file
- sync database or using sequelize
import App from "./src/app";
import db from "./src/models"
import config from "./src/config/config"
const app = App({
logger: true
})
const PORT: string | number = config.port
db.sequelize.sync().then(() => {
app.listen({port:Number(PORT)}, (err) => {
if (err) {
app.log.error(err);
process.exit(1)
}
app.log.info(`SERVE ON ${PORT}`)
})
})
nodemon must use in development only because it will restart the app every time when you update the code
npm install -D nodemon
or
yarn add -D nodemon
"dev": "nodemon index.ts"
npm run dev
or
yarn dev
in this ep will show example article api in get post put patch delete routes -> controllers -> services (calling the model sequelize) -> mysql database
- routes
- request types
- controllers
- services
- api result of get post put delete
inside routes of article i will create for the 4 routes that calling handler function from the article controller
import { FastifyInstance } from "fastify"; // import FastifyInstance
import articleController from './../controllers/article.controller';
const articleRouter = async (app: FastifyInstance) => {
// route api app.method("path", {option}, handler)
app.get(
"/",
articleController.handleGetArticle
);
app.get(
"/get/:id",
articleController.handleGetArticleById
);
app.post(
"/create",
articleController.handleCreateArticle
);
app.put(
"/update/:id",
articleController.handleUpdateArticle
);
app.delete(
"/delete/:id",
articleController.handleDeleteArticle
);
};
export default articleRouter;
import { FastifyRequest } from "fastify";
export type RequestWithIdArticle = FastifyRequest<{
Params: { id: string };
}>;
export type UpdateArticleRequest = FastifyRequest<{
Params: { id: string };
Body: {
title?: string | undefined;
text?: string | undefined;
type?: string | undefined;
};
}>;
export type ArticleCreateRequest = FastifyRequest<{
Body: {
title?: string | undefined;
text?: string | undefined;
type?: string | undefined;
};
}>;
in article controller i will create handle function for call service
import { ArticleCreateRequest, RequestWithIdArticle, UpdateArticleRequest } from "types/articles/article.controller.types";
import { ArticleAttributes } from "types/articles/article.model.types";
import { articleService } from "../services";
export const handleGetArticle = async () => {
return articleService.getArticles()
}
export const handleGetArticleById = async (req: RequestWithIdArticle) => {
const id = req.params.id;
return articleService.getOneArticle(id)
}
export const handleCreateArticle = async (req: ArticleCreateRequest) => {
const { title, text, type } = req.body
return articleService.createArticle({ title, text, type })
}
export const handleUpdateArticle = async (req: UpdateArticleRequest) => {
const { title, text, type } = req.body
return articleService.updateArticle(req.params.id, { title, text, type })
}
export const handleDeleteArticle = async (req: RequestWithIdArticle) => {
return articleService.deleteArticle(req.params.id)
}
export default {
handleGetArticle,
handleGetArticleById,
handleCreateArticle,
handleUpdateArticle,
handleDeleteArticle
}
in article service i create service that call the sequelize for interact with the database
import { ArticleAttributes } from "types/articles/article.model.types";
import db from "../models";
const ArticleModel = db.Article
export const getArticles = async (): Promise<ArticleAttributes[]> => {
const response = await ArticleModel.findAll();
return response
}
export const getOneArticle = async (id: string): Promise<ArticleAttributes> => {
const response: ArticleAttributes = await ArticleModel.findByPk(id)
return response
}
export const createArticle = async (body: ArticleAttributes): Promise<ArticleAttributes> => {
const response: ArticleAttributes = await ArticleModel.create(body)
return response
}
export const updateArticle = async (id: string, body: ArticleAttributes) => {
const response = await ArticleModel.update({ ...body }, { where: { id } })
return response
}
export const deleteArticle = async (id: string) => {
const response = await ArticleModel.destroy({ where: { id } });
return response
}
export default {
getArticles,
getOneArticle,
createArticle,
updateArticle,
deleteArticle
}