Skip to main content

Overview

The backend is a Node.js Express 4 server written in JavaScript (with TypeScript types available) that serves both the API and static frontend files.

Server Entry Point

// backend/src/server.js:1-21
import express from 'express';
import cors from 'cors';
import path from 'path';
import { fileURLToPath } from 'url';
import { PrismaClient } from '@prisma/client';
import dotenv from 'dotenv';
import { Resend } from 'resend';
import jwt from 'jsonwebtoken';
import rateLimit from 'express-rate-limit';

// Load environment file based on NODE_ENV
const envFile = process.env.NODE_ENV === 'production' 
  ? '.env.production' 
  : '.env';
dotenv.config({ path: envFile });

const app = express();
const prisma = new PrismaClient();
const resend = new Resend(process.env.RESEND_API_KEY);

app.use(cors());
app.use(express.json({ limit: '1mb' }));
Key Initializations:
  • Express app with CORS enabled
  • Prisma client for database access
  • Resend client for email delivery
  • JSON body parser with 1MB limit
  • ESM module syntax (import/export)

Middleware

Rate Limiters

// backend/src/server.js:27-33
const loginLimiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 5,
  message: { 
    error: 'Muitas tentativas de login. Tente novamente em 15 minutos.' 
  },
  standardHeaders: true,
  legacyHeaders: false,
});
Applied to: POST /api/auth/loginLimits: 5 attempts per 15 minutes per IP

Authentication Middleware

// backend/src/server.js:58-76
const authMiddleware = (req, res, next) => {
  const authHeader = req.headers.authorization;
  const queryToken = req.query.token;

  const token = authHeader ? authHeader.replace('Bearer ', '') : queryToken;

  if (!token) {
    return res.status(401).json({ error: 'Token não fornecido' });
  }

  try {
    const decoded = jwt.verify(token, process.env.JWT_SECRET);
    req.userId = decoded.id;
    next();
  } catch (error) {
    return res.status(401).json({ error: 'Token inválido' });
  }
};
Features:
  • Accepts token from Authorization: Bearer <token> header OR ?token= query parameter
  • Verifies JWT signature using JWT_SECRET env var
  • Adds userId to request object for downstream handlers
  • Returns 401 for missing/invalid tokens

Utility Functions

HTML Escaping

// backend/src/server.js:43-51
function escapeHtml(str) {
  return String(str)
    .replace(/&/g, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;')
    .replace(/'/g, '&#039;');
}
Purpose: Prevent XSS in email templates by escaping user input

Email Validation

// backend/src/server.js:53-56
function isValidEmail(email) {
  return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}
Purpose: Validate email format before processing contact form

API Routes

Authentication Routes

Project Routes

Contact Route

Admin Email Preview Routes

Email System

Email Templates

Two HTML email templates are defined inline in server.js:
Sent to: André (contato@andreruperto.dev)Purpose: Notify about new contact form submissionFunction: buildNotificationEmail({ name, email, subject, message })Features:
  • Dark theme matching portfolio design
  • Gradient orange header
  • Displays all form fields with labels
  • Safe HTML escaping
  • Reply-to header set to user’s email
Template Structure:
<html>
  <head><!-- Meta tags --></head>
  <body style="background-color: #110F0D;">
    <table width="600">
      <!-- Header: Orange gradient -->
      <tr>
        <td style="background: linear-gradient(135deg, #F97316, #E8553A);">
          <h1>Nova mensagem do portfolio</h1>
        </td>
      </tr>
      <!-- Content: Form data -->
      <tr>
        <td style="background-color: #1A1715;">
          <!-- Name, Email, Subject, Message fields -->
        </td>
      </tr>
      <!-- Footer -->
      <tr>
        <td>Enviado pelo formulário de contato</td>
      </tr>
    </table>
  </body>
</html>
See backend/src/server.js:79-158 for full template.

Resend Integration

import { Resend } from 'resend';
const resend = new Resend(process.env.RESEND_API_KEY);

// Send email
const { data, error } = await resend.emails.send({
  from: 'Portfolio <contato@andreruperto.dev>',
  to: ['contato@andreruperto.dev'],
  replyTo: email,  // User's email for easy reply
  subject: `[Portfolio] ${subject}`,
  html: buildNotificationEmail({ name, email, subject, message })
});
Configuration:
  • Domain: andreruperto.dev
  • From addresses: Portfolio <contato@...>, André Ruperto <contato@...>
  • API key stored in RESEND_API_KEY env var

Static File Serving

// backend/src/server.js:392-397
app.use(express.static(path.join(__dirname, '../dist')));

// Handle React routing - serve index.html for all routes
app.get('*', (req, res) => {
  res.sendFile(path.join(__dirname, '../dist/index.html'));
});
Behavior:
  1. API routes are checked first (defined before * route)
  2. Static files served from backend/dist/ (Vite build output)
  3. All unmatched routes return index.html for client-side routing

Server Startup

// backend/src/server.js:399-406
const PORT = process.env.PORT || 3001;
const ENV = process.env.NODE_ENV || 'development';

app.listen(PORT, () => {
  console.log(`\n✓ Server running on port ${PORT} (${ENV})`);
  console.log(`✓ API: http://localhost:${PORT}/api`);
  console.log(`✓ Frontend: http://localhost:${PORT}\n`);
});
Default Configuration:
  • Port: 3001
  • Environment: development
  • Logs startup info to console

Environment Variables

Required:
  • DATABASE_URL - PostgreSQL connection string
  • RESEND_API_KEY - Resend email service API key
  • ADMIN_PASSWORD - Admin login password
  • JWT_SECRET - Secret for signing JWT tokens
Optional:
  • PORT - Server port (default: 3001)
  • NODE_ENV - Environment mode (development/production)

Error Handling

All async route handlers use try-catch:
app.get('/api/projects', async (req, res) => {
  try {
    const projects = await prisma.project.findMany(...);
    res.json(projects);
  } catch (error) {
    console.error('Erro ao buscar projetos:', error);
    res.status(500).json({ error: 'Erro ao buscar projetos' });
  }
});
Pattern:
  • Log errors to console
  • Return 500 with generic error message
  • Never expose internal error details to client

Next Steps

Database Schema

Learn about Prisma models and database structure

API Reference

Complete API endpoint documentation

Build docs developers (and LLMs) love