├── .eslintrc.js ├── .gitignore ├── README.md ├── package-lock.json ├── package.json ├── requests └── users.http ├── src ├── app.ts ├── config.ts ├── controllers │ └── user.controller.ts ├── db.ts ├── entity │ └── User.ts ├── index.ts └── routes │ └── user.routes.ts └── tsconfig.json /.eslintrc.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line no-undef 2 | module.exports = { 3 | root: true, 4 | parser: "@typescript-eslint/parser", 5 | plugins: ["@typescript-eslint"], 6 | extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended"], 7 | }; 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## TypeORM REST API CRUD -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "typeorm-restapi-crud", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "dev": "ts-node-dev --respawn src/index.ts", 8 | "build": "tsc", 9 | "start": "node dist/index.js" 10 | }, 11 | "keywords": [], 12 | "author": "", 13 | "license": "ISC", 14 | "devDependencies": { 15 | "@types/cors": "^2.8.12", 16 | "@types/express": "^4.17.13", 17 | "@types/morgan": "^1.9.3", 18 | "@types/node": "^17.0.23", 19 | "@typescript-eslint/eslint-plugin": "^5.17.0", 20 | "@typescript-eslint/parser": "^5.17.0", 21 | "eslint": "^8.12.0", 22 | "ts-node-dev": "^1.1.8", 23 | "typescript": "^4.6.3" 24 | }, 25 | "dependencies": { 26 | "cors": "^2.8.5", 27 | "express": "^4.17.3", 28 | "morgan": "^1.10.0", 29 | "pg": "^8.7.3", 30 | "reflect-metadata": "^0.1.13", 31 | "typeorm": "^0.3.4" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /requests/users.http: -------------------------------------------------------------------------------- 1 | @api = http://localhost:3000/api 2 | 3 | ### get all users 4 | GET {{api}}/users 5 | 6 | ### create a new user 7 | POST {{api}}/users 8 | Content-Type: application/json 9 | 10 | { 11 | "firstname": "Joe", 12 | "lastname": "McMillan" 13 | } 14 | 15 | ### get single user 16 | GET {{api}}/users/6 17 | 18 | ### update single user 19 | PUT {{api}}/users/4 20 | Content-Type: application/json 21 | 22 | { 23 | "firstname": "John" 24 | } 25 | 26 | ### delete single user 27 | DELETE {{api}}/users/6 -------------------------------------------------------------------------------- /src/app.ts: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | import morgan from "morgan"; 3 | import cors from "cors"; 4 | 5 | import userRoutes from "./routes/user.routes"; 6 | 7 | const app = express(); 8 | 9 | app.use(cors()); 10 | app.use(morgan("dev")); 11 | app.use(express.json()); 12 | 13 | app.use("/api", userRoutes); 14 | 15 | export default app; 16 | -------------------------------------------------------------------------------- /src/config.ts: -------------------------------------------------------------------------------- 1 | export const PORT = process.env.PORT || 3000; -------------------------------------------------------------------------------- /src/controllers/user.controller.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response } from "express"; 2 | import { User } from "../entity/User"; 3 | 4 | interface UserBody { 5 | firstname: string; 6 | lastname: string; 7 | } 8 | 9 | export const getUsers = async (req: Request, res: Response) => { 10 | try { 11 | const users = await User.find(); 12 | return res.json(users); 13 | } catch (error) { 14 | if (error instanceof Error) { 15 | return res.status(500).json({ message: error.message }); 16 | } 17 | } 18 | }; 19 | 20 | export const getUser = async (req: Request, res: Response) => { 21 | try { 22 | const { id } = req.params; 23 | const user = await User.findOneBy({ id: parseInt(id) }); 24 | 25 | if (!user) return res.status(404).json({ message: "User not found" }); 26 | 27 | return res.json(user); 28 | } catch (error) { 29 | if (error instanceof Error) { 30 | return res.status(500).json({ message: error.message }); 31 | } 32 | } 33 | }; 34 | 35 | export const createUser = async ( 36 | req: Request, 37 | res: Response 38 | ) => { 39 | const { firstname, lastname } = req.body; 40 | const user = new User(); 41 | user.firstname = firstname; 42 | user.lastname = lastname; 43 | await user.save(); 44 | return res.json(user); 45 | }; 46 | 47 | export const updateUser = async (req: Request, res: Response) => { 48 | const { id } = req.params; 49 | 50 | try { 51 | const user = await User.findOneBy({ id: parseInt(id) }); 52 | if (!user) return res.status(404).json({ message: "Not user found" }); 53 | 54 | await User.update({ id: parseInt(id) }, req.body); 55 | 56 | return res.sendStatus(204); 57 | } catch (error) { 58 | if (error instanceof Error) { 59 | return res.status(500).json({ message: error.message }); 60 | } 61 | } 62 | }; 63 | 64 | export const deleteUser = async (req: Request, res: Response) => { 65 | const { id } = req.params; 66 | try { 67 | const result = await User.delete({ id: parseInt(id) }); 68 | 69 | if (result.affected === 0) 70 | return res.status(404).json({ message: "User not found" }); 71 | 72 | return res.sendStatus(204); 73 | } catch (error) { 74 | if (error instanceof Error) { 75 | return res.status(500).json({ message: error.message }); 76 | } 77 | } 78 | }; 79 | -------------------------------------------------------------------------------- /src/db.ts: -------------------------------------------------------------------------------- 1 | import { DataSource } from "typeorm"; 2 | import { User } from "./entity/User"; 3 | 4 | export const AppDataSource = new DataSource({ 5 | type: "postgres", 6 | host: "localhost", 7 | port: 5432, 8 | username: "postgres", 9 | password: "mysecretpassword", 10 | database: "restcrud", 11 | synchronize: true, 12 | // logging: true, 13 | entities: [User], 14 | }); 15 | -------------------------------------------------------------------------------- /src/entity/User.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Entity, 3 | Column, 4 | PrimaryGeneratedColumn, 5 | BaseEntity, 6 | CreateDateColumn, 7 | UpdateDateColumn, 8 | } from "typeorm"; 9 | 10 | @Entity() 11 | export class User extends BaseEntity { 12 | @PrimaryGeneratedColumn() 13 | id: number; 14 | 15 | @Column() 16 | firstname: string; 17 | 18 | @Column() 19 | lastname: string; 20 | 21 | @Column({ default: true }) 22 | active: boolean; 23 | 24 | @CreateDateColumn() 25 | createdAt: Date; 26 | 27 | @UpdateDateColumn() 28 | updatedAt: Date; 29 | } 30 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import app from "./app"; 2 | import { PORT } from "./config"; 3 | import { AppDataSource } from "./db"; 4 | 5 | async function main() { 6 | try { 7 | await AppDataSource.initialize(); 8 | app.listen(PORT); 9 | console.log("Server on port", PORT); 10 | } catch (error) { 11 | console.error(error); 12 | } 13 | } 14 | 15 | main(); 16 | -------------------------------------------------------------------------------- /src/routes/user.routes.ts: -------------------------------------------------------------------------------- 1 | import { Router } from "express"; 2 | import { 3 | getUsers, 4 | getUser, 5 | createUser, 6 | updateUser, 7 | deleteUser, 8 | } from "../controllers/user.controller"; 9 | 10 | const router = Router(); 11 | 12 | router.get("/users", getUsers); 13 | 14 | router.get("/users/:id", getUser); 15 | 16 | router.post("/users", createUser); 17 | 18 | router.put("/users/:id", updateUser); 19 | 20 | router.delete("/users/:id", deleteUser); 21 | 22 | export default router; 23 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Basic Options */ 4 | // "incremental": true, /* Enable incremental compilation */ 5 | "target": "ES6", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */ 6 | "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */ 7 | // "lib": ["ES5", "ES6", "DOM"], /* Specify library files to be included in the compilation. */ 8 | // "allowJs": true, /* Allow javascript files to be compiled. */ 9 | // "checkJs": true, /* Report errors in .js files. */ 10 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ 11 | // "declaration": true, /* Generates corresponding '.d.ts' file. */ 12 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ 13 | // "sourceMap": true, /* Generates corresponding '.map' file. */ 14 | // "outFile": "./", /* Concatenate and emit output to single file. */ 15 | "outDir": "./dist", /* Redirect output structure to the directory. */ 16 | "rootDir": "./src", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 17 | // "composite": true, /* Enable project compilation */ 18 | // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ 19 | // "removeComments": true, /* Do not emit comments to output. */ 20 | // "noEmit": true, /* Do not emit outputs. */ 21 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 22 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 23 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 24 | 25 | /* Strict Type-Checking Options */ 26 | "strict": true, /* Enable all strict type-checking options. */ 27 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 28 | // "strictNullChecks": true, /* Enable strict null checks. */ 29 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 30 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ 31 | "strictPropertyInitialization": false, /* Enable strict checking of property initialization in classes. */ 32 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 33 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 34 | 35 | /* Additional Checks */ 36 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 37 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 38 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 39 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 40 | 41 | /* Module Resolution Options */ 42 | "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 43 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 44 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 45 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 46 | // "typeRoots": [], /* List of folders to include type definitions from. */ 47 | // "types": [], /* Type declaration files to be included in compilation. */ 48 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 49 | "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 50 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 51 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 52 | 53 | /* Source Map Options */ 54 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 55 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 56 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 57 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 58 | 59 | /* Experimental Options */ 60 | "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 61 | "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 62 | 63 | /* Advanced Options */ 64 | "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ 65 | } 66 | } 67 | --------------------------------------------------------------------------------