From 45d6347d4c88ca671a44508d3e3322c15982533e Mon Sep 17 00:00:00 2001 From: Cesar Date: Fri, 22 May 2026 17:13:24 -0600 Subject: [PATCH] feat: add JWT authentication --- backend/package-lock.json | 170 ++++++++++++++++++ backend/package.json | 4 + backend/src/app.ts | 23 ++- backend/src/config/env.ts | 9 + backend/src/config/jwt.ts | 20 +++ backend/src/data/index.ts | 0 backend/src/data/postgres/index.ts | 9 + .../src/domain/dtos/auth/login-user.dto.ts | 15 ++ .../src/domain/dtos/auth/register-user.dto.ts | 21 +++ backend/src/domain/dtos/index.ts | 2 + backend/src/presentation/auth/controller.ts | 137 ++++++++++++++ backend/src/presentation/auth/routes.ts | 20 +++ .../middlewares/auth.middleware.ts | 46 +++++ backend/src/presentation/routes.ts | 14 ++ backend/src/presentation/server.ts | 46 +++++ 15 files changed, 524 insertions(+), 12 deletions(-) create mode 100644 backend/src/config/env.ts create mode 100644 backend/src/config/jwt.ts delete mode 100644 backend/src/data/index.ts create mode 100644 backend/src/data/postgres/index.ts create mode 100644 backend/src/domain/dtos/auth/login-user.dto.ts create mode 100644 backend/src/domain/dtos/auth/register-user.dto.ts create mode 100644 backend/src/domain/dtos/index.ts create mode 100644 backend/src/presentation/auth/controller.ts create mode 100644 backend/src/presentation/auth/routes.ts create mode 100644 backend/src/presentation/middlewares/auth.middleware.ts create mode 100644 backend/src/presentation/routes.ts create mode 100644 backend/src/presentation/server.ts diff --git a/backend/package-lock.json b/backend/package-lock.json index e7af499..9a7d87e 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -11,13 +11,17 @@ "dependencies": { "@prisma/adapter-pg": "^7.8.0", "@prisma/client": "^7.8.0", + "bcryptjs": "^3.0.3", "dotenv": "^17.4.2", "env-var": "^7.5.0", "express": "^5.2.1", + "jsonwebtoken": "^9.0.3", "pg": "^8.21.0" }, "devDependencies": { + "@types/bcryptjs": "^2.4.6", "@types/express": "^5.0.6", + "@types/jsonwebtoken": "^9.0.10", "@types/node": "^25.9.1", "@types/pg": "^8.20.0", "prisma": "^7.8.0", @@ -882,6 +886,13 @@ "devOptional": true, "license": "MIT" }, + "node_modules/@types/bcryptjs": { + "version": "2.4.6", + "resolved": "https://registry.npmjs.org/@types/bcryptjs/-/bcryptjs-2.4.6.tgz", + "integrity": "sha512-9xlo6R2qDs5uixm0bcIqCeMCE6HiQsIyel9KQySStiyqNl2tnj2mP3DX1Nf56MD6KMenNNlBBsy3LJ7gUEQPXQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/body-parser": { "version": "1.19.6", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", @@ -935,6 +946,24 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/jsonwebtoken": { + "version": "9.0.10", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.10.tgz", + "integrity": "sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/ms": "*", + "@types/node": "*" + } + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/node": { "version": "25.9.1", "resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.1.tgz", @@ -1051,6 +1080,15 @@ "node": "18 || 20 || >=22" } }, + "node_modules/bcryptjs": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-3.0.3.tgz", + "integrity": "sha512-GlF5wPWnSa/X5LKM1o0wz0suXIINz1iHRLvTS+sLyi7XPbe5ycmYI3DlZqVGZZtDgl4DmasFg7gOB3JYbphV5g==", + "license": "BSD-3-Clause", + "bin": { + "bcrypt": "bin/bcrypt" + } + }, "node_modules/better-result": { "version": "2.9.2", "resolved": "https://registry.npmjs.org/better-result/-/better-result-2.9.2.tgz", @@ -1095,6 +1133,12 @@ "node": "18 || 20 || >=22" } }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "license": "BSD-3-Clause" + }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -1347,6 +1391,15 @@ "node": ">= 0.4" } }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -1913,6 +1966,91 @@ "devOptional": true, "license": "MIT" }, + "node_modules/jsonwebtoken": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz", + "integrity": "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==", + "license": "MIT", + "dependencies": { + "jws": "^4.0.1", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jwa": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", + "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", + "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", + "license": "MIT", + "dependencies": { + "jwa": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", + "license": "MIT" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", + "license": "MIT" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", + "license": "MIT" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", + "license": "MIT" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "license": "MIT" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "license": "MIT" + }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "license": "MIT" + }, "node_modules/long": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", @@ -2584,6 +2722,26 @@ "node": ">= 18" } }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", @@ -2598,6 +2756,18 @@ "license": "MIT", "peer": true }, + "node_modules/semver": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.1.tgz", + "integrity": "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/send": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", diff --git a/backend/package.json b/backend/package.json index 01d2464..a832cf9 100644 --- a/backend/package.json +++ b/backend/package.json @@ -13,7 +13,9 @@ "license": "ISC", "type": "module", "devDependencies": { + "@types/bcryptjs": "^2.4.6", "@types/express": "^5.0.6", + "@types/jsonwebtoken": "^9.0.10", "@types/node": "^25.9.1", "@types/pg": "^8.20.0", "prisma": "^7.8.0", @@ -24,9 +26,11 @@ "dependencies": { "@prisma/adapter-pg": "^7.8.0", "@prisma/client": "^7.8.0", + "bcryptjs": "^3.0.3", "dotenv": "^17.4.2", "env-var": "^7.5.0", "express": "^5.2.1", + "jsonwebtoken": "^9.0.3", "pg": "^8.21.0" } } diff --git a/backend/src/app.ts b/backend/src/app.ts index f5c7a9f..071cf34 100644 --- a/backend/src/app.ts +++ b/backend/src/app.ts @@ -1,16 +1,15 @@ -import express from 'express'; +import { env } from "./config/env.js"; +import { AppRoutes } from "./presentation/routes.js"; +import { Server } from "./presentation/server.js"; -const app = express(); -const port = process.env.PORT ?? 3000; -app.use(express.json()); - -app.get('/', (_req, res) => { - res.json({ - message: 'API funcionando con Express + TypeScript + TSX', - }); +async function main() { + const server = new Server( { + port : env.PORT, + routes : AppRoutes.routes, }); + await server.start(); -app.listen(port, () => { - console.log(`Servidor corriendo en http://localhost:${port}`); -}); \ No newline at end of file +} + +main(); \ No newline at end of file diff --git a/backend/src/config/env.ts b/backend/src/config/env.ts new file mode 100644 index 0000000..a631686 --- /dev/null +++ b/backend/src/config/env.ts @@ -0,0 +1,9 @@ +import 'dotenv/config'; +import envVar from 'env-var'; + +export const env = { + PORT: envVar.get('PORT').required().asPortNumber(), + PUBLIC_PATH: envVar.get('PUBLIC_PATH').default('public').asString(), + POSTGRES_URL: envVar.get('POSTGRES_URL').required().asString() +} + diff --git a/backend/src/config/jwt.ts b/backend/src/config/jwt.ts new file mode 100644 index 0000000..1b1ff37 --- /dev/null +++ b/backend/src/config/jwt.ts @@ -0,0 +1,20 @@ +import jwt, { SignOptions } from 'jsonwebtoken'; + +const JWT_SECRET = process.env.JWT_SECRET ?? 'dev_secret'; +const JWT_EXPIRES_IN = process.env.JWT_EXPIRES_IN ?? '2h'; + +export class JwtAdapter { + static generateToken(payload: object): string { + return jwt.sign(payload, JWT_SECRET, { + expiresIn: JWT_EXPIRES_IN, + } as SignOptions); + } + + static validateToken(token: string): T | null { + try { + return jwt.verify(token, JWT_SECRET) as T; + } catch { + return null; + } + } +} \ No newline at end of file diff --git a/backend/src/data/index.ts b/backend/src/data/index.ts deleted file mode 100644 index e69de29..0000000 diff --git a/backend/src/data/postgres/index.ts b/backend/src/data/postgres/index.ts new file mode 100644 index 0000000..5d90ca3 --- /dev/null +++ b/backend/src/data/postgres/index.ts @@ -0,0 +1,9 @@ + +import { env } from "../../config/env.js"; +import { PrismaPg } from "@prisma/adapter-pg"; +import { PrismaClient } from "../../generated/prisma/client.js"; + +const connectionString = `${env.POSTGRES_URL}`; +const adapter = new PrismaPg({ connectionString }); + +export const prisma = new PrismaClient({ adapter }); \ No newline at end of file diff --git a/backend/src/domain/dtos/auth/login-user.dto.ts b/backend/src/domain/dtos/auth/login-user.dto.ts new file mode 100644 index 0000000..aec887b --- /dev/null +++ b/backend/src/domain/dtos/auth/login-user.dto.ts @@ -0,0 +1,15 @@ +export class LoginUserDto { + private constructor( + public readonly email: string, + public readonly password: string, + ) {} + + static create(props: { [key: string]: any }): [string | undefined, LoginUserDto?] { + const { email, password } = props; + + if (!email) return ['email is required']; + if (!password) return ['password is required']; + + return [undefined, new LoginUserDto(email, password)]; + } +} \ No newline at end of file diff --git a/backend/src/domain/dtos/auth/register-user.dto.ts b/backend/src/domain/dtos/auth/register-user.dto.ts new file mode 100644 index 0000000..59f623a --- /dev/null +++ b/backend/src/domain/dtos/auth/register-user.dto.ts @@ -0,0 +1,21 @@ +export class RegisterUserDto { + private constructor( + public readonly name: string, + public readonly email: string, + public readonly password: string, + ) {} + + static create(props: { [key: string]: any }): [string | undefined, RegisterUserDto?] { + const { name, email, password } = props; + + if (!name) return ['name is required']; + if (!email) return ['email is required']; + if (!password) return ['password is required']; + + if (password.length < 6) { + return ['password must be at least 6 characters']; + } + + return [undefined, new RegisterUserDto(name, email, password)]; + } +} \ No newline at end of file diff --git a/backend/src/domain/dtos/index.ts b/backend/src/domain/dtos/index.ts new file mode 100644 index 0000000..8243531 --- /dev/null +++ b/backend/src/domain/dtos/index.ts @@ -0,0 +1,2 @@ +export * from './auth/register-user.dto.js'; +export * from './auth/login-user.dto.js'; \ No newline at end of file diff --git a/backend/src/presentation/auth/controller.ts b/backend/src/presentation/auth/controller.ts new file mode 100644 index 0000000..b9c7b2b --- /dev/null +++ b/backend/src/presentation/auth/controller.ts @@ -0,0 +1,137 @@ +import { Request, Response } from "express"; +import bcrypt from "bcryptjs"; + +import { prisma } from "../../data/postgres/index.js"; +import { JwtAdapter } from "../../config/jwt.js"; +import { LoginUserDto, RegisterUserDto } from "../../domain/dtos/index.js"; +import { AuthRequest } from "../middlewares/auth.middleware.js"; + +export class AuthController { + + // SIN metodos estaticos para hacer DI + constructor() {} + + public registerUser = async (req: Request, res: Response) => { + + const [error, registerUserDto] = RegisterUserDto.create(req.body); + + if (error) { + return res.status(400).json({ error }); + } + + try { + const userExists = await prisma.user.findUnique({ + where: { + email: registerUserDto!.email, + } + }); + + if (userExists) { + return res.status(400).json({ + error: 'Email already exists' + }); + } + + const hashedPassword = bcrypt.hashSync(registerUserDto!.password, 10); + + const user = await prisma.user.create({ + data: { + name: registerUserDto!.name, + email: registerUserDto!.email, + password: hashedPassword, + } + }); + + const token = JwtAdapter.generateToken({ + id: user.id, + email: user.email, + role: user.role, + }); + + return res.status(201).json({ + user: { + id: user.id, + name: user.name, + email: user.email, + role: user.role, + }, + token + }); + + } catch (error) { + console.log(error); + return res.status(500).json({ + error: 'Internal server error' + }); + } + } + + public loginUser = async (req: Request, res: Response) => { + + const [error, loginUserDto] = LoginUserDto.create(req.body); + + if (error) { + return res.status(400).json({ error }); + } + + try { + const user = await prisma.user.findUnique({ + where: { + email: loginUserDto!.email, + } + }); + + if (!user) { + return res.status(400).json({ + error: 'Invalid email or password' + }); + } + + const isPasswordValid = bcrypt.compareSync( + loginUserDto!.password, + user.password + ); + + if (!isPasswordValid) { + return res.status(400).json({ + error: 'Invalid email or password' + }); + } + + const token = JwtAdapter.generateToken({ + id: user.id, + email: user.email, + role: user.role, + }); + + return res.json({ + user: { + id: user.id, + name: user.name, + email: user.email, + role: user.role, + }, + token + }); + + } catch (error) { + console.log(error); + return res.status(500).json({ + error: 'Internal server error' + }); + } + } + + public getMe = async (req: AuthRequest, res: Response) => { + + if (!req.user) { + return res.status(401).json({ + error: 'User not authenticated' + }); + } + + return res.json({ + user: req.user + }); + } +} \ No newline at end of file diff --git a/backend/src/presentation/auth/routes.ts b/backend/src/presentation/auth/routes.ts new file mode 100644 index 0000000..2913c41 --- /dev/null +++ b/backend/src/presentation/auth/routes.ts @@ -0,0 +1,20 @@ +import { Router } from "express"; +import { AuthController } from "./controller.js"; +import { AuthMiddleware } from "../middlewares/auth.middleware.js"; + +export class AuthRoutes { + + static get routes(): Router { + + const router = Router(); + const controller = new AuthController(); + + router.post('/register', controller.registerUser); + router.post('/login', controller.loginUser); + + // Ruta protegida para probar JWT + router.get('/me', AuthMiddleware.validateJwt, controller.getMe); + + return router; + } +} \ No newline at end of file diff --git a/backend/src/presentation/middlewares/auth.middleware.ts b/backend/src/presentation/middlewares/auth.middleware.ts new file mode 100644 index 0000000..c1daaa4 --- /dev/null +++ b/backend/src/presentation/middlewares/auth.middleware.ts @@ -0,0 +1,46 @@ +import { Request, Response, NextFunction } from "express"; +import { JwtAdapter } from "../../config/jwt.js"; + +interface TokenPayload { + id: number; + email: string; + role: string; +} + +export interface AuthRequest extends Request { + user?: TokenPayload; +} + +export class AuthMiddleware { + + static validateJwt = (req: AuthRequest, res: Response, next: NextFunction) => { + + const authorization = req.header('Authorization'); + + if (!authorization) { + return res.status(401).json({ + error: 'No token provided' + }); + } + + if (!authorization.startsWith('Bearer ')) { + return res.status(401).json({ + error: 'Invalid Bearer token' + }); + } + + const token = authorization.split(' ').at(1) ?? ''; + + const payload = JwtAdapter.validateToken(token); + + if (!payload) { + return res.status(401).json({ + error: 'Invalid token' + }); + } + + req.user = payload; + + next(); + } +} \ No newline at end of file diff --git a/backend/src/presentation/routes.ts b/backend/src/presentation/routes.ts new file mode 100644 index 0000000..0bf1063 --- /dev/null +++ b/backend/src/presentation/routes.ts @@ -0,0 +1,14 @@ +import { Router } from "express"; +import { AuthRoutes } from "./auth/routes.js"; + +export class AppRoutes { + + static get routes(): Router { + + const router = Router(); + + router.use('/api/auth', AuthRoutes.routes); + + return router; + } +} \ No newline at end of file diff --git a/backend/src/presentation/server.ts b/backend/src/presentation/server.ts new file mode 100644 index 0000000..10c0127 --- /dev/null +++ b/backend/src/presentation/server.ts @@ -0,0 +1,46 @@ + +import express, { Router } from 'express'; + +interface Options { + port: number; + routes: Router; +} + + +export class Server { + private app = express(); + private readonly port: number; + private readonly routes: Router; + + constructor (options : Options) { + const {port, routes} = options; + this.port = port; + this.routes = routes; + } + + + + + + async start() { + /*middleware*/ + // para recebir el body como json + this.app.use(express.json()); + // para recebir el body como urlencoded + this.app.use(express.urlencoded({ extended: true })); + //* Public folder + + + // Routes + this.app.use(this.routes); + + + + // ponner servido a escuchar en el puerto 8080 + this.app.listen(this.port, () => { + console.log(`Server is running on port ${this.port}`); + }); + } + +} +