Repository: Sean-Bradley/Seans-TypeScript-NodeJS-CRUD-REST-API-Boilerplate
Branch: master
Commit: 0542164d3c94
Files: 20
Total size: 24.1 KB
Directory structure:
gitextract_m3qilazq/
├── .github/
│ └── FUNDING.yml
├── .gitignore
├── .gitlab-ci.yml
├── Dockerfile
├── LICENSE
├── app.json
├── docker-compose.yml
├── nginx/
│ ├── Dockerfile
│ ├── nginx.conf
│ └── static/
│ └── index.html
├── package.json
├── readme.md
├── src/
│ ├── app.ts
│ ├── models/
│ │ ├── Bird.ts
│ │ ├── Cat.ts
│ │ └── Dog.ts
│ ├── router.ts
│ ├── server.ts
│ └── swagger.json
└── tsconfig.json
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/FUNDING.yml
================================================
# These are supported funding model platforms
github: [Sean-Bradley]# Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: seanwasere
open_collective: # Replace with a single Open Collective username
ko_fi: sean_bradley
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
================================================
FILE: .gitignore
================================================
node_modules
npm-debug.log
dist/
================================================
FILE: .gitlab-ci.yml
================================================
image: docker:latest
services:
- docker:dind
stages:
- test
- deploy
step-develop:
stage: test
before_script:
- export DYNAMIC_ENV_VAR=DEVELOP
only:
- develop
tags:
- develop
script:
- echo running tests in $DYNAMIC_ENV_VAR
step-uat:
stage: deploy
before_script:
- export DYNAMIC_ENV_VAR=UAT
only:
- uat
tags:
- uat
script:
- echo setting up env $DYNAMIC_ENV_VAR
- sudo apt-get install -y python-pip
- sudo pip install docker-compose
- sudo docker image prune -f
- sudo docker-compose -f docker-compose.yml build --no-cache
- sudo docker-compose -f docker-compose.yml up -d
step-deploy-staging:
stage: deploy
before_script:
- export DYNAMIC_ENV_VAR=STAGING
only:
- staging
tags:
- staging
script:
- echo setting up env $DYNAMIC_ENV_VAR
- sudo apt-get install -y python-pip
- sudo pip install docker-compose
- sudo docker image prune -f
- sudo docker-compose -f docker-compose.yml build --no-cache
- sudo docker-compose -f docker-compose.yml up -d
step-deploy-production:
stage: deploy
before_script:
- export DYNAMIC_ENV_VAR=PRODUCTION
only:
- production
tags:
- production
script:
- echo setting up env $DYNAMIC_ENV_VAR
- sudo apt-get install -y python-pip
- sudo pip install docker-compose
- sudo docker image prune -f
- sudo docker-compose -f docker-compose.yml build --no-cache
- sudo docker-compose -f docker-compose.yml up -d
when: manual
================================================
FILE: Dockerfile
================================================
FROM node:alpine
LABEL github=https://github.com/Sean-Bradley
COPY src /nodejs/src
COPY package.json /nodejs/package.json
COPY tsconfig.json /nodejs/tsconfig.json
WORKDIR /nodejs
RUN npm install
EXPOSE 3000:3000
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2019 seanwasere youtube
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: app.json
================================================
{
"name": "seans-typescript-nodejs-crud-rest-api-boilerplate",
"description": "A barebones TypeScript Node.js CRUD REST API",
"repository": "https://github.com/Sean-Bradley/Seans-TypeScript-NodeJS-CRUD-REST-API-Boilerplate",
"logo": "https://github.com/Sean-Bradley/Seans-TypeScript-NodeJS-CRUD-REST-API-Boilerplate/blob/master/cosmo1.png",
"keywords": ["node", "typescript", "crud", "seanwasere"]
}
================================================
FILE: docker-compose.yml
================================================
version: '2'
services:
nginx:
build:
context: nginx
dockerfile: Dockerfile
ports:
- "80:80"
command: nginx -g "daemon off";
depends_on:
- nodejs
nodejs:
build:
context: .
dockerfile: Dockerfile
image: service-cats:1.01
expose:
- "3000"
command: npm start
================================================
FILE: nginx/Dockerfile
================================================
FROM nginx
LABEL github=https://github.com/Sean-Bradley
COPY /nginx.conf /etc/nginx/nginx.conf
#COPY /server.crt /etc/nginx/server.crt
#COPY /server.key /etc/nginx/server.key
COPY /static /static
================================================
FILE: nginx/nginx.conf
================================================
user www-data;
worker_processes 1;
pid /run/nginx.pid;
events {
worker_connections 768;
}
http {
sendfile off;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
include /etc/nginx/mime.types;
default_type application/octet-stream;
#access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
gzip on;
gzip_disable "msie6";
server {
listen 80;
server_name localhost;
#ssl_certificate server.crt;
#ssl_certificate_key server.key;
location / {
root /static;
index index.html;
}
location /api/ {
proxy_pass_header Server;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Scheme $scheme;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_redirect off;
proxy_connect_timeout 20;
proxy_read_timeout 20;
proxy_pass http://nodejs:3000/;
}
}
}
================================================
FILE: nginx/static/index.html
================================================
put the static html generated from you favourite front end framework here.
================================================
FILE: package.json
================================================
{
"name": "seans-typescript-nodejs-crud-rest-api-boilerplate",
"version": "1.0.1",
"description": "",
"main": "server.js",
"scripts": {
"start": "tsc && node dist/server.js",
"build": "tsc",
"dev": "concurrently --kill-others \"tsc -w\" \"nodemon dist/server.js\""
},
"keywords": [
"typescript",
"nodejs",
"crud",
"rest",
"swagger"
],
"author": "Sean Bradley",
"license": "ISC",
"dependencies": {
"@types/express": "^4.17.11",
"@types/node": "^13.13.40",
"body-parser": "^1.20.2",
"cors": "^2.8.5",
"express": "^4.18.2",
"swagger-ui-express": "^5.0.0",
"typescript": "^5.2.2",
"uuid": "^9.0.1"
},
"devDependencies": {
"concurrently": "^8.2.2",
"nodemon": "^3.0.1"
}
}
================================================
FILE: readme.md
================================================
## Seans-TypeScript-NodeJS-CRUD-REST-API-Boilerplate
> To help support this TypeScript boilerplate, please take a moment to look at my official **Design Patterns in TypeScript** book and **TypeScript Courses**.
>
[Three.js and TypeScript](https://www.udemy.com/course/threejs-tutorials/?referralCode=4C7E1DE91C3E42F69D0F)
>
>
[Socket.IO and TypeScript](https://www.udemy.com/course/typescript-socketio/?referralCode=2F6E227AC7EB9D147327)
>
>
Three.js and TypeScript : [ASIN B094716FD6](https://www.amazon.com/dp/B09GYTKRCH)
>
>
Design Patterns in TypeScript : Paperback [ASIN B0948BCH24](https://www.amazon.com/dp/B0948BCH24), eBook : [ASIN B094716FD6](https://www.amazon.com/dp/B094716FD6)
### MIT License
Remember, No guarantees, or even fit for a particular purpose.
If you have a suggestion, or you want to contribute some code, you can make a pull request.
Your contributions will be visible since this project is public.
### Setup
```bash
npm install
```
### Development with nodemon and tsc --watch
```bash
npm run dev
```
Then visit `http://localhost:3000/cats`
### Run without nodemon and tsc --watch
```bash
npm start
```
Then visit `http://localhost:3000/cats`
## Swagger
Visit `http://localhost:3000/swagger` to view the OPENAPI document in Swagger-UI

### Video tutorial on setting up Swagger in an existing NodeJS TypeScript API
[](https://youtu.be/qemG0CWOx1I)
## Continuous Integration and Deployment
I've also added gitlab-ci.yml and dockerised with Docker-Compose. See video tutorial on how all this works.
[](https://youtu.be/Qlj6NiOy5jM)
## Usage
### List all records

### Post (Create) Record

### Get by Id

### Put (Update) Record

### Delete Record

# TypeScript Courses
If you got this far, you probably like TypeScript just like I do,
I have created two TypeScript courses specializing in the [Three.js](https://www.udemy.com/course/threejs-tutorials/?referralCode=4C7E1DE91C3E42F69D0F) and [Socket.IO](https://www.udemy.com/course/typescript-socketio/?referralCode=2F6E227AC7EB9D147327)
libraries that you may find useful.
## Threejs and TypeScript Course
[](https://youtu.be/BcF3yuVqfwo)
## Socket.io and TypeScript Course
[](https://youtu.be/3uLSNctzkkw)
# Programming Books
To help support my projects, please check out my books.
## Three.js and TypeScript
https://www.amazon.com/dp/B09GYTKRCH
https://www.amazon.co.uk/dp/B09GYTKRCH
https://www.amazon.in/dp/B09GYTKRCH
https://www.amazon.de/dp/B09GYTKRCH
https://www.amazon.fr/dp/B09GYTKRCH
https://www.amazon.es/dp/B09GYTKRCH
https://www.amazon.it/dp/B09GYTKRCH
https://www.amazon.nl/dp/B09GYTKRCH
https://www.amazon.co.jp/dp/B09GYTKRCH
https://www.amazon.ca/dp/B09GYTKRCH
https://www.amazon.com.br/dp/B09GYTKRCH
https://www.amazon.com.mx/dp/B09GYTKRCH
https://www.amazon.com.au/dp/B09GYTKRCH
(ASIN : B09GZM9KGJ / B09GYTKRCH)
**Design Patterns in TypeScript**.
https://www.amazon.com/dp/B0948BCH24
https://www.amazon.co.uk/dp/B0948BCH24
https://www.amazon.in/dp/B094716FD6
https://www.amazon.de/dp/B0948BCH24
https://www.amazon.fr/dp/B0948BCH24
https://www.amazon.es/dp/B0948BCH24
https://www.amazon.it/dp/B0948BCH24
https://www.amazon.co.jp/dp/B0948BCH24
https://www.amazon.ca/dp/B0948BCH24
https://www.amazon.com.au/dp/B094716FD6
(ASIN : B0948BCH24 / B094716FD6)
---
Thanks
Sean
================================================
FILE: src/app.ts
================================================
import express from 'express'
import Router from './router'
import swaggerUi from 'swagger-ui-express'
import * as swaggerDocument from './swagger.json'
import * as bodyParser from 'body-parser'
class App {
private httpServer: any
constructor() {
this.httpServer = express()
this.httpServer.use(bodyParser.urlencoded({ extended: true }));
this.httpServer.use(bodyParser.json());
new Router(this.httpServer);
this.httpServer.use('/swagger', swaggerUi.serve, swaggerUi.setup(swaggerDocument));
}
public Start = (port: number) => {
return new Promise((resolve, reject) => {
this.httpServer.listen(
port,
() => {
resolve(port)
})
.on('error', (err: object) => reject(err));
})
}
}
export default App;
================================================
FILE: src/models/Bird.ts
================================================
type Bird = {
genus: String;
name: String;
isHungry: Boolean;
lastFedDate: Date;
}
export default Bird
================================================
FILE: src/models/Cat.ts
================================================
type Cat = {
genus: String;
name: String;
isHungry: Boolean;
lastFedDate: Date;
}
export default Cat
================================================
FILE: src/models/Dog.ts
================================================
type Dog = {
genus: String;
name: String;
isHungry: Boolean;
lastFedDate: Date;
}
export default Dog
================================================
FILE: src/router.ts
================================================
import * as express from 'express'
import Cat from './models/Cat'
import { v4 as uuid } from 'uuid';
import cors from 'cors'
class Router {
constructor(server: express.Express) {
const router = express.Router()
const cats = new Map();
cats[uuid()] = { genus: "feline", name: "Cosmo", isHungry: true, lastFedDate: new Date() }
cats[uuid()] = { genus: "feline", name: "Emmy", isHungry: true, lastFedDate: new Date() }
router.get('/', (req: express.Request, res: express.Response) => {
res.json({
message: `Nothing to see here, [url]/cats instead.`
})
})
//get all cats
router.get('/cats', cors(), (req: express.Request, res: express.Response) => {
res.json({
cats
})
})
//create new cat
router.post('/cats', cors(), (req: express.Request, res: express.Response) => {
try {
let cat: Cat = {} as Cat;
Object.assign(cat, req.body)
const newUUID = uuid();
cats[newUUID] = cat;
res.json({
uuid: newUUID
})
} catch (e) {
res.status(400).send(JSON.stringify({ "error": "problem with posted data" }));
}
})
//get cat by id
router.get('/cats/:id', cors(), (req: express.Request, res: express.Response) => {
if (!!cats[req.params.id]) {
res.json({
cat: cats[req.params.id]
})
} else {
res.status(404).send(JSON.stringify({ "error": "no such cat" }));
}
})
//update cat
router.put('/cats/:id', cors(), (req: express.Request, res: express.Response) => {
try {
if (!!cats[req.params.id]) {
let cat: Cat = {} as Cat;
Object.assign(cat, req.body)
cats[req.params.id] = cat;
res.json({
cat: cats[req.params.id]
})
} else {
res.status(404).send(JSON.stringify({ "error": "no such cat" }));
}
} catch (e) {
res.status(400).send(JSON.stringify({ "error": "problem with posted data" }));
}
})
//delete cat
router.delete('/cats/:id', cors(), (req: express.Request, res: express.Response) => {
if (!!cats[req.params.id]) {
delete cats[req.params.id]
res.json({
uuid: req.params.id
})
} else {
res.status(404).send(JSON.stringify({ "error": "no such cat" }));
}
});
router.options('*', cors());
server.use('/', router)
}
}
export default Router;
================================================
FILE: src/server.ts
================================================
import app from './app'
const port = parseInt(process.env.PORT || '3000')
const server = new app().Start(port)
.then(port => console.log(`Server running on port ${port}`))
.catch(error => {
console.log(error)
process.exit(1);
});
export default server;
================================================
FILE: src/swagger.json
================================================
{
"openapi": "3.0.0",
"info": {
"version": "1.0.0",
"title": "Seans-TypeScript-NodeJS-CRUD-REST-API-Boilerplate",
"description": "A minimal and easy to follow example of what you need to create a CRUD style API in NodeJs using TypeScript",
"license": {
"name": "MIT",
"url": "https://opensource.org/licenses/MIT"
}
},
"servers": [
{
"url": "/",
"description": "Local Dev, or from Heroku"
},
{
"url": "/api/",
"description": "With docker-compose and nginx proxy"
}
],
"tags": [
{
"name": "Cats",
"description": "API for cats in the system"
}
],
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"paths": {
"/cats": {
"get": {
"tags": [
"Cats"
],
"summary": "Get all cats in system",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/Cats"
}
}
}
},
"post": {
"tags": [
"Cats"
],
"summary": "Create a new cat in system",
"requestBody": {
"description": "Cat Object",
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/definitions/Cat"
}
}
}
},
"produces": [
"application/json"
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/id"
}
},
"400": {
"description": "Failed. Bad post data."
}
}
}
},
"/cats/{id}": {
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"description": "ID of the cat that we want to match",
"type": "string"
}
],
"get": {
"tags": [
"Cats"
],
"summary": "Get cat with given ID",
"parameters": [
{
"in": "path",
"name": "id",
"required": true,
"description": "Cat with id",
"schema": {
"$ref": "#/definitions/id"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/Cat"
}
},
"404": {
"description": "Failed. Cat not found."
}
}
},
"put": {
"summary": "Update cat with given ID",
"tags": [
"Cats"
],
"requestBody": {
"description": "Cat Object",
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/definitions/Cat"
}
}
}
},
"parameters": [
{
"in": "path",
"name": "id",
"required": true,
"description": "Cat with new values of properties",
"schema": {
"$ref": "#/definitions/id"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/Cat"
}
},
"400": {
"description": "Failed. Bad post data."
},
"404": {
"description": "Failed. Cat not found."
}
}
},
"delete": {
"summary": "Delete cat with given ID",
"tags": [
"Cats"
],
"parameters": [
{
"in": "path",
"name": "id",
"required": true,
"description": "Delete Cat with id",
"schema": {
"$ref": "#/definitions/id"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/id"
}
},
"404": {
"description": "Failed. Cat not found."
}
}
}
}
},
"definitions": {
"id": {
"properties": {
"uuid": {
"type": "string"
}
}
},
"Cat": {
"type": "object",
"properties": {
"genus": {
"type": "string"
},
"name": {
"type": "string"
},
"isHungry": {
"type": "boolean"
},
"lastFedDate": {
"type": "string"
}
}
},
"Cats": {
"type": "object",
"properties": {
"cats": {
"type": "object",
"additionalProperties": {
"$ref": "#/definitions/Cat"
}
}
}
}
}
}
================================================
FILE: tsconfig.json
================================================
{
"compilerOptions": {
"target": "es2017",
"module": "commonjs",
"resolveJsonModule": true,
"esModuleInterop": true,
"outDir": "dist",
//"sourceMap": true
},
"files": [
"./node_modules/@types/node/index.d.ts"
],
"include": [
"src/**/*.ts"
],
"exclude": [
"node_modules"
]
}