Building the Event Management App: Connecting Node.js to PostgreSQL

By sanjeev

Sanjeev kumar

July 3, 2025

Published

postgresnodejsDevopssequelize

In our previous post, we created a basic Node.js Express application for our event scheduling app. Now it's time to transform it into an enterprise-grade application by adding PostgreSQL connectivity with proper architecture patterns.

Why Enterprise Architecture Matters

While we're focusing on DevOps practices, it's crucial to understand how real-world applications are structured. The patterns we'll implement today are used by companies like Netflix, Spotify, and Airbnb. Young developers need to see:

  • Separation of Concerns: Controllers, Models, Routes, Authentication

  • Security-First Approach: JWT authentication and protected endpoints

  • Production-Ready Patterns: Error handling, validation, and database relationships

  • Scalable Architecture: Code organization that grows with your team

Building on Our Foundation

Our basic Express app from the previous post will evolve into a 3-tier enterprise application:

📁 src/
├── 🎮 Controllers/     # Business Logic Layer
├── 🗃️ Models/         # Data Access Layer  
├── 🛣️ Routes/         # API Layer
├── 🔐 auth/           # Security Layer
└── 📄 index.js        # Application Entry Point

This structure mirrors what you'll find in Fortune 500 companies!

What We're Building

Our event management application will handle:

  • Users: Registration, login, and profile management

  • Events: Create, update, and manage events with proper ownership

  • Invitations: Secure invitation system with status tracking

  • Authentication: JWT-based security (industry standard)

Setting Up Enterprise-Grade Database Connectivity

Step 1: Install Enterprise Dependencies

Building on our existing package.json, let's add the production-grade packages:

# Database and ORM
npm install --save pg sequelize

# Security and Authentication  
npm install --save bcrypt jsonwebtoken

# Configuration Management
npm install --save config

# Development Tools
npm install --save-dev sequelize-cli

These are the same packages used by major tech companies for production applications.

Step 2: Enterprise Configuration Management

Create a proper configuration structure that works across environments:

// config/dev.json
{
  "auth": {
    "secretKey": "your-super-secret-jwt-key-change-in-production"
  },
  "dbConfig": {
    "dbUser": "api",
    "host": "localhost", 
    "port": 5432,
    "dbName": "api",
    "dbPassword": "password"
  }
}
// config/production.json
{
  "auth": {
    "secretKey": "process.env.JWT_SECRET"
  },
  "dbConfig": {
    "dbUser": "process.env.DB_USER",
    "host": "process.env.DB_HOST",
    "port": "process.env.DB_PORT", 
    "dbName": "process.env.DB_NAME",
    "dbPassword": "process.env.DB_PASSWORD"
  }
}

Enterprise Lesson: Configuration management is critical for deploying across multiple environments (dev, staging, production).

Step 3: Database Models with Enterprise Patterns

Let's create production-ready models with proper validation and relationships:

// src/Models/userModel.js
module.exports = (sequelize, DataTypes) => {
  const User = sequelize.define(
    "User",
    {
      id: {
        type: DataTypes.UUID,
        defaultValue: DataTypes.UUIDV4,
        primaryKey: true
      },
      name: {
        type: DataTypes.STRING,
        allowNull: false,
        validate: {
          notEmpty: true,
          len: [2, 100]
        }
      },
      email: {
        type: DataTypes.STRING,
        unique: true,
        allowNull: false,
        validate: {
          isEmail: true
        }
      },
      password: {
        type: DataTypes.STRING,
        allowNull: true // For demo - in production, this would be required
      }
    },
    { 
      timestamps: true,
      tableName: 'users'
    }
  );

  return User;
};
// src/Models/eventModel.js  
module.exports = (sequelize, DataTypes) => {
  const Event = sequelize.define(
    "Event",
    {
      id: {
        type: DataTypes.UUID,
        defaultValue: DataTypes.UUIDV4,
        primaryKey: true
      },
      title: {
        type: DataTypes.STRING,
        allowNull: false,
        validate: {
          notEmpty: true,
          len: [3, 200]
        }
      },
      description: {
        type: DataTypes.TEXT,
        allowNull: true
      },
      eventDate: {
        type: DataTypes.DATE,
        allowNull: false,
        validate: {
          isDate: true,
          isAfter: new Date().toISOString().split('T')[0]
        }
      },
      location: {
        type: DataTypes.STRING,
        allowNull: true
      },
      organizerId: {
        type: DataTypes.UUID,
        allowNull: false,
        references: {
          model: 'users',
          key: 'id'
        }
      },
      status: {
        type: DataTypes.ENUM,
        values: ['draft', 'published', 'cancelled'],
        defaultValue: 'published'
      }
    },
    { 
      timestamps: true,
      tableName: 'events'
    }
  );

  return Event;
};

Enterprise Lesson: Notice the UUIDs (not integers), validation rules, and proper foreign key relationships. This is how Netflix and Spotify structure their data models.

Step 4: Database Connection with Enterprise Patterns

// src/Models/index.js
const { Sequelize, DataTypes } = require("sequelize");
const config = require("config");

// Enterprise-grade connection with proper error handling
const sequelize = new Sequelize(
  config.dbConfig.dbName, 
  config.dbConfig.dbUser, 
  config.dbConfig.dbPassword, 
  {
    dialect: 'postgres',
    port: config.dbConfig.port,
    host: config.dbConfig.host,
    // Production-ready connection pooling
    pool: {
      max: 5,
      min: 0,
      acquire: 30000,
      idle: 10000
    },
    // Environment-based logging
    logging: config.env === 'production' ? false : console.log
  }
);

// Test connection with proper error handling
sequelize
  .authenticate()
  .then(() => {
    console.log(`✅ Database connected successfully to ${config.dbConfig.dbName}`);
  })
  .catch((err) => {
    console.error('❌ Unable to connect to database:', err);
  });

const db = {};
db.Sequelize = Sequelize;
db.sequelize = sequelize;

// Import all models
db.users = require("./userModel")(sequelize, DataTypes);
db.events = require("./eventModel")(sequelize, DataTypes);
db.invitations = require("./invitationModel")(sequelize, DataTypes);

// Define enterprise-grade relationships
db.users.hasMany(db.events, { foreignKey: 'organizerId', as: 'organizedEvents' });
db.events.belongsTo(db.users, { foreignKey: 'organizerId', as: 'organizer' });

db.events.hasMany(db.invitations, { foreignKey: 'eventId' });
db.invitations.belongsTo(db.events, { foreignKey: 'eventId' });

db.users.hasMany(db.invitations, { foreignKey: 'userId' });
db.invitations.belongsTo(db.users, { foreignKey: 'userId' });

module.exports = db;

Enterprise Lesson: Connection pooling, environment-based configuration, and relationship management are essential for scalable applications.

Step 5: Enterprise Authentication Layer

// src/auth/auth.js
const jwt = require("jsonwebtoken");
const config = require("config");
const db = require("../Models");

const User = db.users;
const authConfig = config.get("auth");

// Middleware to validate JWT tokens (used by Google, Facebook, etc.)
const validateUser = async (req, res, next) => {
  try {
    const authHeader = req.headers["authorization"];
    
    if (!authHeader) {
      return res.status(401).json({
        status: 'error',
        message: 'No authorization header provided'
      });
    }

    const token = authHeader.split(" ")[1]; // Bearer <token>
    
    if (!token) {
      return res.status(401).json({
        status: 'error', 
        message: 'No token provided'
      });
    }

    // Verify JWT token
    jwt.verify(token, authConfig.secretKey, (err, decoded) => {
      if (err) {
        return res.status(403).json({
          status: 'error',
          message: 'Invalid or expired token'
        });
      }
      
      req.user = decoded; // Add user info to request
      next(); // Continue to the actual endpoint
    });
    
  } catch (error) {
    console.error('Authentication error:', error);
    res.status(500).json({
      status: 'error',
      message: 'Authentication service error'
    });
  }
};

module.exports = { validateUser };

Enterprise Lesson: JWT tokens are the industry standard for API authentication. This is how modern microservices authenticate users.

Step 6: Enterprise Controller Pattern

// src/Controllers/eventController.js
const db = require("../Models");

const Event = db.events;
const User = db.users;

// Enterprise-grade controller with proper error handling
const createEvent = async (req, res) => {
  try {
    const { title, description, eventDate, location, organizerId } = req.body;
    
    // Input validation (essential for production apps)
    if (!title || !eventDate || !organizerId) {
      return res.status(400).json({
        status: 'error',
        message: 'Title, event date, and organizer ID are required'
      });
    }

    // Business logic validation
    const organizer = await User.findByPk(organizerId);
    if (!organizer) {
      return res.status(400).json({
        status: 'error',
        message: 'Organizer not found'
      });
    }

    // Create the event
    const newEvent = await Event.create({
      title,
      description, 
      eventDate,
      location,
      organizerId
    });

    // Consistent API response format
    res.status(201).json({
      status: 'success',
      data: { event: newEvent }
    });
    
  } catch (error) {
    console.error('Error creating event:', error);
    res.status(500).json({
      status: 'error',
      message: 'Failed to create event'
    });
  }
};

// Get all events with relationships (enterprise pattern)
const getAllEvents = async (req, res) => {
  try {
    const events = await Event.findAll({
      include: [
        {
          model: User,
          as: 'organizer',
          attributes: ['id', 'name', 'email'] // Never expose passwords
        }
      ],
      order: [['eventDate', 'ASC']]
    });

    res.status(200).json({
      status: 'success',
      data: { events }
    });
    
  } catch (error) {
    console.error('Error fetching events:', error);
    res.status(500).json({
      status: 'error',
      message: 'Failed to fetch events'
    });
  }
};

module.exports = {
  createEvent,
  getAllEvents
  // ... other methods
};

Enterprise Lesson: Controllers handle business logic, validate input, and return consistent API responses. This pattern scales to teams of 100+ developers.

Step 7: Enterprise Route Protection

// src/Routes/eventRoutes.js
const express = require("express");
const eventController = require("../Controllers/eventController");
const userAuth = require("../auth/auth");

const router = express.Router();

// All routes are protected with JWT authentication
router.post("/", userAuth.validateUser, eventController.createEvent);
router.get("/all", userAuth.validateUser, eventController.getAllEvents);

module.exports = router;

Enterprise Lesson: Route-level protection ensures only authenticated users can access your APIs. This is standard in all major tech companies.

Step 8: Enterprise Application Structure

// src/index.js - Main application file
const express = require("express");
const cookieParser = require("cookie-parser");
const db = require("./Models");
const userRoutes = require("./Routes/userRoutes");
const eventRoutes = require("./Routes/eventRoutes");

const app = express();
const PORT = process.env.PORT || 4000;

// Enterprise middleware stack
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(cookieParser());

// CORS configuration for production
app.use(function (req, res, next) {
  res.setHeader('Access-Control-Allow-Origin', 'http://localhost:4200');
  res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, PATCH, DELETE');
  res.setHeader('Access-Control-Allow-Headers', 'X-Requested-With,content-type,Authorization');
  res.setHeader('Access-Control-Allow-Credentials', true);
  next();
});

// Health check endpoint (required for enterprise deployments)
app.get("/", (request, response) => {
  response.json({ 
    info: "Node.js, Express, and Postgres API",
    status: "healthy",
    timestamp: new Date().toISOString()
  });
});

// API routes with proper prefixing
app.use('/api/users', userRoutes);
app.use('/api/events', eventRoutes);

// Database sync and server start
db.sequelize.sync({ force: false }).then(() => {
  console.log("✅ Database synchronized");
  
  app.listen(PORT, () => {
    console.log(`🚀 Enterprise server running on port ${PORT}`);
  });
});

Testing Our Enterprise Application

# Start the application
npm run start

# Test authentication endpoint
curl -X POST http://localhost:4000/api/users/signup \
  -H "Content-Type: application/json" \
  -d '{"name":"John Doe","email":"[email protected]","password":"secure123"}'

# Test protected endpoint (requires JWT token)
curl -X GET http://localhost:4000/api/events/all \
  -H "Authorization: Bearer YOUR_JWT_TOKEN"

What's Next in Our DevOps Journey?

Now that we have an enterprise-grade application, our DevOps practices become even more valuable:

  1. Containerization: Docker strategies for complex applications

  2. Security: Managing JWT secrets in production

  3. Database Migrations: Handling schema changes safely

  4. Load Balancing: Scaling enterprise applications

  5. Monitoring: Tracking authentication, errors, and performance

  6. CI/CD: Automated testing for complex applications

Key Takeaways

Enterprise Architecture: Real-world patterns used by major tech companies
Security-First: JWT authentication and protected endpoints
Scalable Design: Code organization that grows with your team
Production-Ready: Error handling, validation, and monitoring hooks
DevOps Foundation: Perfect base for containerization and deployment

For Young Developers 🎓

This is how real enterprise applications are built! You've learned:

  • Why companies separate Controllers, Models, and Routes

  • How JWT authentication works in production systems

  • Why input validation and error handling matter

  • How to structure code for team collaboration

These patterns will serve you well in any tech company, from startups to FAANG!


Next up: We'll containerize this enterprise application with Docker, showing how production applications are deployed at scale.

Share This Article

Share this post

Help others discover this content by sharing it with your network