DEV Community

Satish
Satish

Posted on

Building a Scalable Auth Service Using Node.js, Express, PostgreSQL, and Prisma (Microservices Architecture)

Below is a production-style Auth Service for your Local Marketplace Microservices Platform using:

  • Node.js
  • Express
  • PostgreSQL
  • Prisma ORM
  • JWT Authentication
  • Docker-ready structure
  • Layered architecture (Controller → Service → Repository)

This structure is similar to what engineers use in production systems at companies like Stripe and Shopify.
📁 Auth Service Folder Structure
auth-service

├── src
│ ├── config
│ │ └── db.js
│ │
│ ├── controllers
│ │ └── auth.controller.js
│ │
│ ├── services
│ │ └── auth.service.js
│ │
│ ├── repositories
│ │ └── user.repository.js
│ │
│ ├── routes
│ │ └── auth.routes.js
│ │
│ ├── middlewares
│ │ └── auth.middleware.js
│ │
│ ├── utils
│ │ ├── hash.js
│ │ └── jwt.js
│ │
│ ├── app.js
│ └── server.js

├── prisma
│ ├── schema.prisma
│ └── seed.js

├── .env
├── package.json
└── Dockerfile

1️⃣ package.json

{
  "name": "auth-service",
  "version": "1.0.0",
  "main": "src/server.js",
  "scripts": {
    "dev": "nodemon src/server.js",
    "start": "node src/server.js",
    "prisma": "prisma migrate dev"
  },
  "dependencies": {
    "@prisma/client": "^5.0.0",
    "bcryptjs": "^2.4.3",
    "dotenv": "^16.0.0",
    "express": "^4.18.2",
    "jsonwebtoken": "^9.0.0"
  },
  "devDependencies": {
    "nodemon": "^3.0.0",
    "prisma": "^5.0.0"
  }
}
Enter fullscreen mode Exit fullscreen mode

2️⃣ .env

PORT=4000

DATABASE_URL="postgresql://postgres:password@localhost:5432/auth_db"

JWT_SECRET=supersecret
Enter fullscreen mode Exit fullscreen mode

3️⃣ Prisma Schema
📁 prisma/schema.prisma

generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

model User {
  id        String   @id @default(uuid())
  name      String
  email     String   @unique
  password  String
  role      String   @default("CUSTOMER")
  createdAt DateTime @default(now())
}
Enter fullscreen mode Exit fullscreen mode

Run:

npx prisma generate
npx prisma migrate dev --name init
Enter fullscreen mode Exit fullscreen mode

4️⃣ DB Config

📁 src/config/db.js

const { PrismaClient } = require('@prisma/client')

const prisma = new PrismaClient()

module.exports = prisma
Enter fullscreen mode Exit fullscreen mode

5️⃣ Hash Utility

📁 src/utils/hash.js

const bcrypt = require('bcryptjs')

exports.hashPassword = async (password) => {
  return bcrypt.hash(password, 10)
}

exports.comparePassword = async (password, hash) => {
  return bcrypt.compare(password, hash)
}
Enter fullscreen mode Exit fullscreen mode

6️⃣ JWT Utility

📁 src/utils/jwt.js

const jwt = require('jsonwebtoken')

exports.generateToken = (payload) => {
  return jwt.sign(payload, process.env.JWT_SECRET, {
    expiresIn: "1d"
  })
}
Enter fullscreen mode Exit fullscreen mode

7️⃣ Repository Layer

📁 src/repositories/user.repository.js

const prisma = require('../config/db')

exports.createUser = (data) => {
  return prisma.user.create({ data })
}

exports.findUserByEmail = (email) => {
  return prisma.user.findUnique({
    where: { email }
  })
}
Enter fullscreen mode Exit fullscreen mode

8️⃣ Service Layer

📁 src/services/auth.service.js

const userRepo = require('../repositories/user.repository')
const { hashPassword, comparePassword } = require('../utils/hash')
const { generateToken } = require('../utils/jwt')

exports.register = async (data) => {

  const existingUser = await userRepo.findUserByEmail(data.email)

  if (existingUser) {
    throw new Error("User already exists")
  }

  const hashedPassword = await hashPassword(data.password)

  const user = await userRepo.createUser({
    ...data,
    password: hashedPassword
  })

  return user
}

exports.login = async ({ email, password }) => {

  const user = await userRepo.findUserByEmail(email)

  if (!user) {
    throw new Error("Invalid credentials")
  }

  const valid = await comparePassword(password, user.password)

  if (!valid) {
    throw new Error("Invalid credentials")
  }

  const token = generateToken({
    id: user.id,
    email: user.email
  })

  return { token }
}
Enter fullscreen mode Exit fullscreen mode

9️⃣ Controller

📁 src/controllers/auth.controller.js

const authService = require('../services/auth.service')

exports.register = async (req, res) => {
  try {

    const user = await authService.register(req.body)

    res.status(201).json(user)

  } catch (error) {

    res.status(400).json({
      message: error.message
    })

  }
}

exports.login = async (req, res) => {

  try {

    const result = await authService.login(req.body)

    res.json(result)

  } catch (error) {

    res.status(401).json({
      message: error.message
    })

  }
}
Enter fullscreen mode Exit fullscreen mode

🔟 Routes

📁 src/routes/auth.routes.js

const express = require('express')
const router = express.Router()

const authController = require('../controllers/auth.controller')

router.post('/register', authController.register)

router.post('/login', authController.login)

module.exports = router
Enter fullscreen mode Exit fullscreen mode

1️⃣1️⃣ Express App

📁 src/app.js

const express = require('express')
const authRoutes = require('./routes/auth.routes')

const app = express()

app.use(express.json())

app.use('/auth', authRoutes)

module.exports = app
Enter fullscreen mode Exit fullscreen mode

1️⃣2️⃣ Server

📁 src/server.js

require('dotenv').config()

const app = require('./app')

const PORT = process.env.PORT || 4000

app.listen(PORT, () => {
  console.log(`Auth service running on port ${PORT}`)
})
Enter fullscreen mode Exit fullscreen mode

1️⃣3️⃣ Dockerfile

FROM node:20

WORKDIR /app

COPY package*.json ./

RUN npm install

COPY . .

EXPOSE 4000

CMD ["npm","start"]
Enter fullscreen mode Exit fullscreen mode

🚀 API Endpoints
Register User

POST /auth/register
Enter fullscreen mode Exit fullscreen mode

Body

{
  "name": "John",
  "email": "john@example.com",
  "password": "123456"
}
Enter fullscreen mode Exit fullscreen mode

Login

POST /auth/login
Enter fullscreen mode Exit fullscreen mode

Body

{
  "email": "john@example.com",
  "password": "123456"
}
Enter fullscreen mode Exit fullscreen mode

⭐ What Makes This Production-Ready

✔ Layered architecture
✔ Repository pattern
✔ Prisma ORM
✔ JWT authentication
✔ Microservice-ready
✔ Docker-ready
✔ Scalable structure

Top comments (0)