From 5fac9695b651ca796c92dc60a1b0909eebba11ec Mon Sep 17 00:00:00 2001 From: kamesh diviyanajana Date: Thu, 2 Apr 2026 11:04:21 +0530 Subject: [PATCH] Feat: add postgresql folders --- .vscode/settings.json | 3 + src/templates/js/postgresql/.gitignore | 127 ++++++++++++++++++ src/templates/js/postgresql/.gitkeep | 0 src/templates/js/postgresql/default.env | 10 ++ src/templates/js/postgresql/package.json | 29 ++++ src/templates/js/postgresql/src/app.js | 42 ++++++ src/templates/js/postgresql/src/configs/db.js | 30 +++++ .../src/controllers/auth.controller.js | 72 ++++++++++ .../src/controllers/user.controller.js | 27 ++++ .../src/middlewares/auth.middleware.js | 64 +++++++++ .../src/repositories/user.repository.js | 42 ++++++ .../js/postgresql/src/routes/auth.route.js | 9 ++ .../js/postgresql/src/routes/user.route.js | 24 ++++ .../postgresql/src/services/user.service.js | 50 +++++++ .../js/postgresql/src/utils/asyncHandler.js | 5 + .../js/postgresql/src/utils/response.js | 13 ++ 16 files changed, 547 insertions(+) create mode 100644 .vscode/settings.json create mode 100644 src/templates/js/postgresql/.gitignore delete mode 100644 src/templates/js/postgresql/.gitkeep create mode 100644 src/templates/js/postgresql/default.env create mode 100644 src/templates/js/postgresql/package.json create mode 100644 src/templates/js/postgresql/src/app.js create mode 100644 src/templates/js/postgresql/src/configs/db.js create mode 100644 src/templates/js/postgresql/src/controllers/auth.controller.js create mode 100644 src/templates/js/postgresql/src/controllers/user.controller.js create mode 100644 src/templates/js/postgresql/src/middlewares/auth.middleware.js create mode 100644 src/templates/js/postgresql/src/repositories/user.repository.js create mode 100644 src/templates/js/postgresql/src/routes/auth.route.js create mode 100644 src/templates/js/postgresql/src/routes/user.route.js create mode 100644 src/templates/js/postgresql/src/services/user.service.js create mode 100644 src/templates/js/postgresql/src/utils/asyncHandler.js create mode 100644 src/templates/js/postgresql/src/utils/response.js diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..32a970b --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "snyk.advanced.autoSelectOrganization": true +} diff --git a/src/templates/js/postgresql/.gitignore b/src/templates/js/postgresql/.gitignore new file mode 100644 index 0000000..f69a12e --- /dev/null +++ b/src/templates/js/postgresql/.gitignore @@ -0,0 +1,127 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) +web_modules/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional stylelint cache +.stylelintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# parcel-bundler cache (https://parceljs.org/) +.cache +.parcel-cache + + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and not Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# vuepress v2.x temp and cache directory +.temp +.cache + +# Docusaurus cache and generated files +.docusaurus + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port + +# Stores VSCode versions used for testing VSCode extensions +.vscode-test + +# yarn v2 +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.* + + +## OS X +.DS_Store diff --git a/src/templates/js/postgresql/.gitkeep b/src/templates/js/postgresql/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/templates/js/postgresql/default.env b/src/templates/js/postgresql/default.env new file mode 100644 index 0000000..43ced7c --- /dev/null +++ b/src/templates/js/postgresql/default.env @@ -0,0 +1,10 @@ +PORT=8000 +JWT_SECRET=your_jwt_secret_here +JWT_EXPIRES_IN=1d + + +DB_HOST=localhost +DB_PORT=5432 +DB_USER=postgres +DB_PASSWORD= 12345 +DB_NAME= \ No newline at end of file diff --git a/src/templates/js/postgresql/package.json b/src/templates/js/postgresql/package.json new file mode 100644 index 0000000..0c6fef5 --- /dev/null +++ b/src/templates/js/postgresql/package.json @@ -0,0 +1,29 @@ +{ + "name": "js-postgresql-api", + "version": "1.0.0", + "description": "Modern Express API with PostgreSQL, ES Modules, and async CRUD", + "type": "module", + "main": "./src/app.js", + "scripts": { + "dev": "nodemon app.js", + "start": "node app.js", + "lint": "eslint .", + "format": "prettier --write ." + }, + "dependencies": { + "bcryptjs": "^3.0.3", + "cors": "^2.8.5", + "dotenv": "^16.4.5", + "express": "^4.19.2", + "http-errors": "^2.0.0", + "jsonwebtoken": "^9.0.3", + "pg": "^8.20.0", + "morgan": "^1.10.0" + }, + "devDependencies": { + "eslint": "^9.0.0", + "eslint-plugin-import": "^2.29.1", + "nodemon": "^3.1.14", + "prettier": "^3.2.5" + } +} \ No newline at end of file diff --git a/src/templates/js/postgresql/src/app.js b/src/templates/js/postgresql/src/app.js new file mode 100644 index 0000000..cb5e6fb --- /dev/null +++ b/src/templates/js/postgresql/src/app.js @@ -0,0 +1,42 @@ +import express from "express"; +import morgan from "morgan"; +import createError from "http-errors"; +import cors from "cors"; +import dotenv from "dotenv"; +import apiRoutes from "./routes/user.route.js"; +import authRoutes from "./routes/auth.route.js"; +import { connectDB } from "./configs/db.js"; + +dotenv.config(); +connectDB(); + +const app = express(); + +// Middlewares +app.use(cors()); +app.use(express.json()); +app.use(express.urlencoded({ extended: true })); +app.use(morgan("dev")); + +// Routes +app.get("/", (req, res) => res.json({ message: "API is running 🚀" })); +app.use("/api", apiRoutes); // user routes +app.use("/api/auth", authRoutes); // login route + +// 404 handler +app.use((req, res, next) => { + next(createError.NotFound()); +}); + +// Global error handler +app.use((err, req, res) => { + res.status(err.status || 500).json({ + success: false, + message: err.message || "Internal Server Error", + }); +}); + +const PORT = process.env.PORT || 3000; +app.listen(PORT, () => + console.log(`🚀 Server running @ http://localhost:${PORT}`) +); diff --git a/src/templates/js/postgresql/src/configs/db.js b/src/templates/js/postgresql/src/configs/db.js new file mode 100644 index 0000000..9aae01b --- /dev/null +++ b/src/templates/js/postgresql/src/configs/db.js @@ -0,0 +1,30 @@ +// src/config/db.js +import pkg from "pg"; +import 'dotenv/config'; + + +const { Pool } = pkg; + +const pool = new Pool({ + user: String(process.env.DB_USER), + host: String(process.env.DB_HOST), + database: String(process.env.DB_NAME), + password: String(process.env.DB_PASSWORD), + port: Number(process.env.DB_PORT), +}); + +export const connectDB = async () => { + try { + const client = await pool.connect(); + //remove this in production + console.log("🚀 Connected to PostgreSQL database"); + + client.release(); + } catch (err) { + //remove this in production + console.error("DB connection error:", err); + process.exit(1); + } +}; + +export default pool; diff --git a/src/templates/js/postgresql/src/controllers/auth.controller.js b/src/templates/js/postgresql/src/controllers/auth.controller.js new file mode 100644 index 0000000..b05c546 --- /dev/null +++ b/src/templates/js/postgresql/src/controllers/auth.controller.js @@ -0,0 +1,72 @@ +import asyncHandler from "../utils/asyncHandler.js"; +import jwt from "jsonwebtoken"; +import bcrypt from "bcryptjs"; +import { successResponse, errorResponse } from "../utils/response.js"; +import{ findByEmail } from "../repositories/user.repository.js"; + +// Login endpoint +export const login = asyncHandler(async (req, res) => { + try { + const { email, password } = req.body; + + // Find user + // const user = await User.findOne({ email }); + const user = await findByEmail(email); + if (!user) { + return res.status(401).json(errorResponse(null, "Invalid credentials")); + } + + // Check password + const isMatch = await bcrypt.compare(password, user.password); + if (!isMatch) { + return res.status(401).json(errorResponse(null, "Invalid credentials")); + } + + // Generate token + const token = jwt.sign( + { id: user._id, email: user.email }, + process.env.JWT_SECRET, + { expiresIn: process.env.JWT_EXPIRES_IN || "1d" } + ); + + res.json(successResponse({ token }, "Login successful")); + } catch (error) { + res.status(500).json(errorResponse(error, "Login failed")); + } +}); + +export const signup = asyncHandler(async (req, res) => { + try { + const { name, email, password, age } = req.body; + + // Check if user already exists + const existingUser = await User.findOne({ email }); + if (existingUser) { + return res.status(400).json(errorResponse(null, "User already exists")); + } + + // Hash password + const hashedPassword = await bcrypt.hash(password, 10); + + // Create user + const newUser = await User.create({ + name, + email, + password: hashedPassword, + age: age || 0, + }); + + // Generate token + const token = jwt.sign( + { id: newUser._id, email: newUser.email }, + process.env.JWT_SECRET, + { expiresIn: process.env.JWT_EXPIRES_IN || "1d" } + ); + + res + .status(201) + .json(successResponse({ token, user: newUser }, "Signup successful")); + } catch (error) { + res.status(500).json(errorResponse(error, "Login failed")); + } +}); diff --git a/src/templates/js/postgresql/src/controllers/user.controller.js b/src/templates/js/postgresql/src/controllers/user.controller.js new file mode 100644 index 0000000..3b6b52a --- /dev/null +++ b/src/templates/js/postgresql/src/controllers/user.controller.js @@ -0,0 +1,27 @@ +import * as userService from "../services/user.service.js"; +import asyncHandler from "../utils/asyncHandler.js"; + +export const getUsers = asyncHandler(async (req, res) => { + const result = await userService.getUsers(); + res.status(result.status).json(result); +}); + +export const getUser = asyncHandler(async (req, res) => { + const result = await userService.getUser(req.params.id); + res.status(result.status).json(result); +}); + +export const createUser = asyncHandler(async (req, res) => { + const result = await userService.createNewUser(req.body); + res.status(result.status).json(result); +}); + +export const updateUser = asyncHandler(async (req, res) => { + const result = await userService.updateExistingUser(req.params.id, req.body); + res.status(result.status).json(result); +}); + +export const deleteUser = asyncHandler(async (req, res) => { + const result = await userService.deleteExistingUser(req.params.id); + res.status(result.status).json(result); +}); diff --git a/src/templates/js/postgresql/src/middlewares/auth.middleware.js b/src/templates/js/postgresql/src/middlewares/auth.middleware.js new file mode 100644 index 0000000..f977b3e --- /dev/null +++ b/src/templates/js/postgresql/src/middlewares/auth.middleware.js @@ -0,0 +1,64 @@ +/** + * Auth middleware to protect routes using JWT + * + * How to use: + * 1. Generate a JWT token after user login using `jwt.sign(payload, JWT_SECRET, { expiresIn })`. + * 2. Send the token in the request headers: Authorization: Bearer + * 3. Add this middleware to routes you want to protect: + * router.get('/protected', protect, protectedController) + * 4. Inside your controller, `req.user` will have the decoded user info + */ + +import jwt from "jsonwebtoken"; +import { errorResponse } from "../utils/response.js"; + +export const protect = (req, res, next) => { + try { + const authHeader = req.headers.authorization; + + if (!authHeader || !authHeader.startsWith("Bearer ")) { + return res + .status(401) + .json( + errorResponse( + { status: 401 }, + "Authorization token missing or malformed" + ) + ); + } + + // Get token from headers + const token = authHeader.split(" ")[1]; + + if (!token) { + return res + .status(401) + .json(errorResponse({ status: 401 }, "Not authorized, token missing")); + } + + // Verify token + let decoded; + try { + decoded = jwt.verify(token, process.env.JWT_SECRET); + } catch (err) { + if (err.name === "TokenExpiredError") { + return res + .status(401) + .json(errorResponse({ status: 401 }, "Token expired")); + } + return res + .status(401) + .json(errorResponse({ status: 401 }, "Unauthorized: Invalid token")); + } + + // Attach decoded user info to request + req.user = decoded; + + next(); + } catch (err) { + // Catch any unexpected errors + return res + .status(500) + .json(errorResponse({ status: 500 }, "Server error: " + err.message)); + } +}; diff --git a/src/templates/js/postgresql/src/repositories/user.repository.js b/src/templates/js/postgresql/src/repositories/user.repository.js new file mode 100644 index 0000000..c009fbb --- /dev/null +++ b/src/templates/js/postgresql/src/repositories/user.repository.js @@ -0,0 +1,42 @@ +import pool from "../configs/db.js"; + +//table need to be created before using these functions +// CREATE TABLE users ( +// id SERIAL PRIMARY KEY, +// name VARCHAR(100) NOT NULL, +// email VARCHAR(100) UNIQUE NOT NULL +// ); +export const getAllUsers = async () => { + const result = await pool.query("SELECT * FROM users ORDER BY id"); + return result.rows; +}; + +export const getUserById = async (id) => { + const result = await pool.query("SELECT * FROM users WHERE id=$1", [id]); + return result.rows[0]; +}; + +export const createUser = async ({ name, email }) => { + const result = await pool.query( + "INSERT INTO users(name, email) VALUES($1, $2) RETURNING *", + [name, email] + ); + return result.rows[0]; +}; + +export const updateUser = async (id, { name, email }) => { + const result = await pool.query( + "UPDATE users SET name=$1, email=$2 WHERE id=$3 RETURNING *", + [name, email, id] + ); + return result.rows[0]; +}; + +export const deleteUser = async (id) => { + await pool.query("DELETE FROM users WHERE id=$1", [id]); +}; + +export const findByEmail = async (email) => { + const result = await pool.query("SELECT * FROM users WHERE email=$1", [email]); + return result.rows[0]; +}; diff --git a/src/templates/js/postgresql/src/routes/auth.route.js b/src/templates/js/postgresql/src/routes/auth.route.js new file mode 100644 index 0000000..1ea6767 --- /dev/null +++ b/src/templates/js/postgresql/src/routes/auth.route.js @@ -0,0 +1,9 @@ +import { Router } from "express"; +import { login, signup } from "../controllers/auth.controller.js"; + +const router = Router(); + +router.post("/login", login); +router.post("/signup", signup); + +export default router; diff --git a/src/templates/js/postgresql/src/routes/user.route.js b/src/templates/js/postgresql/src/routes/user.route.js new file mode 100644 index 0000000..0d06382 --- /dev/null +++ b/src/templates/js/postgresql/src/routes/user.route.js @@ -0,0 +1,24 @@ +import { Router } from "express"; +import { + getUsers, + getUser, + createUser, + updateUser, + deleteUser, +} from "../controllers/user.controller.js"; +import { protect } from "../middlewares/auth.middleware.js"; +import { successResponse } from "../utils/response.js"; + +const router = Router(); + +router.get("/protected", protect, (req, res) => { + res.json(successResponse({ user: req.user }, "Protected route accessed")); +}); + +router.get("/users", getUsers); +router.get("/users/:id", protect, getUser); +router.post("/users", createUser); +router.put("/users/:id", updateUser); +router.delete("/users/:id", deleteUser); + +export default router; diff --git a/src/templates/js/postgresql/src/services/user.service.js b/src/templates/js/postgresql/src/services/user.service.js new file mode 100644 index 0000000..58080e6 --- /dev/null +++ b/src/templates/js/postgresql/src/services/user.service.js @@ -0,0 +1,50 @@ +import * as userRepo from "../repositories/user.repository.js"; +import { successResponse, errorResponse } from "../utils/response.js"; + +export const getUsers = async () => { + try { + const users = await userRepo.getAllUsers(); + return successResponse(users, "Users fetched successfully"); + } catch (err) { + return errorResponse(err, "Failed to fetch users"); + } +}; + +export const getUser = async (id) => { + try { + const user = await userRepo.getUserById(id); + if (!user) throw new Error("User not found"); + return successResponse(user, "User fetched successfully"); + } catch (err) { + return errorResponse(err, "Failed to fetch user"); + } +}; + +export const createNewUser = async (data) => { + try { + const user = await userRepo.createUser(data); + return successResponse(user, "User created successfully"); + } catch (err) { + return errorResponse(err, "Failed to create user"); + } +}; + +export const updateExistingUser = async (id, data) => { + try { + const user = await userRepo.updateUser(id, data); + if (!user) throw new Error("User not found"); + return successResponse(user, "User updated successfully"); + } catch (err) { + return errorResponse(err, "Failed to update user"); + } +}; + +export const deleteExistingUser = async (id) => { + try { + const user = await userRepo.deleteUser(id); + if (!user) throw new Error("User not found"); + return successResponse(user, "User deleted successfully"); + } catch (err) { + return errorResponse(err, "Failed to delete user"); + } +}; diff --git a/src/templates/js/postgresql/src/utils/asyncHandler.js b/src/templates/js/postgresql/src/utils/asyncHandler.js new file mode 100644 index 0000000..1203157 --- /dev/null +++ b/src/templates/js/postgresql/src/utils/asyncHandler.js @@ -0,0 +1,5 @@ +const asyncHandler = (fn) => (req, res, next) => { + Promise.resolve(fn(req, res, next)).catch(next); +}; + +export default asyncHandler; diff --git a/src/templates/js/postgresql/src/utils/response.js b/src/templates/js/postgresql/src/utils/response.js new file mode 100644 index 0000000..20188dc --- /dev/null +++ b/src/templates/js/postgresql/src/utils/response.js @@ -0,0 +1,13 @@ +export const successResponse = (data, message = "Success") => ({ + status: 200, + success: true, + message, + data, +}); + +export const errorResponse = (error, message = "Error") => ({ + status: error.status || 500, + success: false, + message, + error: error.message || message, +});