Build SMS appointment reminders with Node.js, Express, and MessageBird
Online appointment booking offers convenience but often suffers from no-shows, leading to lost revenue and time. Sending timely SMS reminders is a simple yet effective way to nudge customers and significantly reduce missed appointments.
This guide provides a complete walkthrough for building a Node.js and Express web application that enables users to book appointments and automatically schedules SMS reminders via the MessageBird API. We'll cover everything from project setup to deployment considerations, focusing on the MessageBird API's scheduling capabilities.
Final Outcome: A functional web application where users can submit an appointment form, and the system schedules an SMS reminder to be sent via MessageBird three hours prior to the appointment time.
Project overview and goals
Goal: To build a web application that captures appointment details and leverages MessageBird to schedule and send SMS reminders automatically, thereby reducing customer no-shows.
Problem Solved: Addresses the issue of easily forgotten online appointments by implementing automated, timely SMS notifications.
Technologies:
- Node.js: A JavaScript runtime environment for building the server-side application.
- Express.js: A minimal and flexible Node.js web application framework used to handle routing and middleware.
- MessageBird SDK for Node.js: Simplifies interaction with the MessageBird REST API for sending SMS and validating phone numbers.
- Moment.js: A library for parsing, validating, manipulating, and displaying dates and times.
- Handlebars: A templating engine to render dynamic HTML pages for the user interface.
- dotenv: A module to load environment variables from a
.env
file intoprocess.env
.
Why these technologies?
- Node.js & Express: Provide a popular, efficient, and scalable foundation for web applications, well-suited for I/O-bound tasks like API interactions.
- MessageBird: Offers robust APIs for communication, including reliable SMS delivery and phone number lookup, with built-in scheduling functionality.
- Moment.js: Simplifies complex date and time calculations needed for validation and scheduling.
- Handlebars: Offers a straightforward way to create the user-facing web forms and confirmation pages.
- dotenv: Facilitates secure credential management, a best practice for production applications.
System Architecture:
(Note: This simplified diagram shows the basic flow.)
+-----------------+ +---------------------+ +-----------------+
| User Browser |----->| Node.js/Express App |----->| MessageBird API|
| (HTML Form) |<-----| (Web Server) |<-----| (SMS_ Lookup) |
+-----------------+ +---------------------+ +-----------------+
| |
| Book Appointment | Validate Number_ Schedule SMS
| (POST /book) |
| | Store Appointment (In-Memory/DB)
V V
+-----------------+ +---------------------+
| Confirmation Page|<- --| Render UI |
+-----------------+ +---------------------+
|
| (3 hours before appointment)
V
+-----------------+
| User's Phone |
| (Receives SMS) |
+-----------------+
Prerequisites:
- Node.js (v16.x or later recommended) and npm (or yarn) installed.
- A MessageBird account.
- Basic understanding of JavaScript_ Node.js_ and web concepts (HTTP_ forms).
1. Setting up the project
Let's initialize our Node.js project and install the necessary dependencies.
-
Create Project Directory: Open your terminal and create a new directory for the project_ then navigate into it.
mkdir messagebird-reminders cd messagebird-reminders
-
Initialize Node.js Project: This command creates a
package.json
file to manage project dependencies and metadata.npm init -y
-
Install Dependencies: We need Express for the web server_ the MessageBird SDK_ Moment for date handling_ dotenv for environment variables_ and express-handlebars for templating. Note that
body-parser
functionality is built into modern Express versions and installed as part ofexpress
.npm install express messagebird moment dotenv express-handlebars
-
Create Project Structure: Set up a basic directory structure for clarity.
mkdir views mkdir views/layouts touch index.js .env .gitignore touch views/home.handlebars views/confirm.handlebars views/layouts/main.handlebars
index.js
: The main application entry point.views/
: Contains Handlebars template files.views/layouts/
: Contains layout templates (like headers/footers)..env
: Stores sensitive credentials (API keys). Never commit this file to version control..gitignore
: Specifies files/directories to be ignored by Git.
-
Configure
.gitignore
: Addnode_modules
and.env
to your.gitignore
file to prevent committing them.# .gitignore node_modules/ .env
-
Configure
.env
: You'll need your MessageBird API key. Retrieve or create one from the API access (REST) tab in the MessageBird Dashboard. You can also optionally specify a default country code for the Lookup API.# .env MESSAGEBIRD_API_KEY=YOUR_LIVE_API_KEY_HERE COUNTRY_CODE=US # Replace with your default country code (e.g._ NL_ GB)
MESSAGEBIRD_API_KEY
: Your live or test API key from MessageBird.COUNTRY_CODE
: (Optional) An ISO 3166-1 alpha-2 country code. Providing this enables users to enter phone numbers in local format without the country prefix.
Architectural Decisions:
- We are using a standard Express application structure with server logic in
index.js
and views separated into theviews
directory. - Handlebars is chosen for its simplicity in rendering dynamic HTML.
- Environment variables (
dotenv
) are used for credentials_ adhering to the Twelve-Factor App methodology for configuration.
2. Implementing core functionality
Now_ let's build the main application logic: the web form_ server setup_ and the route that handles appointment booking and SMS scheduling.
-
Set up Express Server (
index.js
): Configure the Express app_ Handlebars templating_ and middleware.// index.js require('dotenv').config(); // Load .env variables const express = require('express'); const exphbs = require('express-handlebars'); // body-parser is built into Express >= 4.16, accessed via express.json() and express.urlencoded() const moment = require('moment'); // --- MessageBird Setup --- // Validate that the API key exists if (!process.env.MESSAGEBIRD_API_KEY) { console.error(""Error: MESSAGEBIRD_API_KEY is not set in the .env file.""); process.exit(1); // Exit if the key is missing } const messagebird = require('messagebird')(process.env.MESSAGEBIRD_API_KEY); // --- End MessageBird Setup --- const app = express(); const port = process.env.PORT || 8080; // --- Templating Engine Setup --- app.engine('handlebars', exphbs.engine({ defaultLayout: 'main', helpers: { // Helper to format date/time for display if needed formatDateTime: function (dateTime, format) { return moment(dateTime).format(format); }, // Helper to check equality for dropdown selection eq: function (a, b) { return a === b; } } })); app.set('view engine', 'handlebars'); // --- End Templating Engine Setup --- // --- Middleware --- // Use built-in Express middleware to parse URL-encoded form data app.use(express.urlencoded({ extended: true })); // Serve static files if needed (e.g., CSS) // app.use(express.static('public')); // --- End Middleware --- // --- In-Memory Appointment Storage (Replace with Database for Production) --- const AppointmentDatabase = []; // --- End In-Memory Storage --- // --- Routes (Defined Below) --- // GET / : Display the booking form app.get('/', (req, res) => { // Pass any previously submitted (but failed) data back to the form res.render('home', { error: req.query.error, // Display error messages if redirected name: req.query.name, treatment: req.query.treatment, number: req.query.number, date: req.query.date, time: req.query.time }); }); // POST /book : Handle form submission, validate, schedule reminder app.post('/book', (req, res) => { const { name, treatment, number, date, time } = req.body; // Step 1: Basic Input Validation if (!name || !treatment || !number || !date || !time) { console.log(""Validation Failed: Missing fields""); // Redirect back with error and existing data return res.redirect(`/?error=Please fill out all fields.&name=${encodeURIComponent(name)}&treatment=${encodeURIComponent(treatment)}&number=${encodeURIComponent(number)}&date=${encodeURIComponent(date)}&time=${encodeURIComponent(time)}`); } // Step 2: Date/Time Validation const appointmentDT = moment(`${date} ${time}`, 'YYYY-MM-DD HH:mm'); // Schedule reminder 3 hours before appointment const reminderDT = appointmentDT.clone().subtract({ hours: 3 }); // Set minimum booking time (e.g., 3 hours 5 mins from now) to allow scheduling const earliestPossibleDT = moment().add({ hours: 3, minutes: 5 }); if (!appointmentDT.isValid() || appointmentDT.isBefore(earliestPossibleDT)) { console.log(""Validation Failed: Invalid or past date/time"", appointmentDT.format()); return res.redirect(`/?error=Please select a valid date and time at least 3 hours 5 minutes from now.&name=${encodeURIComponent(name)}&treatment=${encodeURIComponent(treatment)}&number=${encodeURIComponent(number)}&date=${encodeURIComponent(date)}&time=${encodeURIComponent(time)}`); } // Step 3: Phone Number Validation (MessageBird Lookup) console.log(`Validating phone number: ${number} with Country Code: ${process.env.COUNTRY_CODE || 'None'}`); messagebird.lookup.read(number, process.env.COUNTRY_CODE, (err, lookupResponse) => { if (err) { console.error(""Lookup API Error:"", err); let errorMessage = ""Something went wrong while checking your phone number!""; // Specific error for invalid format (Error code 21) if (err.errors && err.errors[0].code === 21) { errorMessage = ""You need to enter a valid phone number format!""; } return res.redirect(`/?error=${encodeURIComponent(errorMessage)}&name=${encodeURIComponent(name)}&treatment=${encodeURIComponent(treatment)}&number=${encodeURIComponent(number)}&date=${encodeURIComponent(date)}&time=${encodeURIComponent(time)}`); } // Check if the number is mobile if (lookupResponse.type !== 'mobile') { console.log(`Validation Failed: Number ${lookupResponse.phoneNumber} is not mobile (type: ${lookupResponse.type})`); const errorMessage = ""Please provide a mobile number so we can send SMS reminders.""; return res.redirect(`/?error=${encodeURIComponent(errorMessage)}&name=${encodeURIComponent(name)}&treatment=${encodeURIComponent(treatment)}&number=${encodeURIComponent(number)}&date=${encodeURIComponent(date)}&time=${encodeURIComponent(time)}`); } // All checks passed, proceed to schedule console.log(`Phone number ${lookupResponse.phoneNumber} validated successfully.`); const recipientNumber = lookupResponse.phoneNumber; // Use normalized number // Step 4: Schedule the Reminder (MessageBird Messages API) const messageParams = { originator: 'BeautyBird', // Alphanumeric sender ID (check country support) or a purchased virtual number recipients: [recipientNumber], scheduledDatetime: reminderDT.toISOString(), // Use ISO 8601 format body: `${name}, here's a reminder for your ${treatment} appointment scheduled for ${appointmentDT.format('HH:mm')}. See you soon!` }; console.log(""Scheduling message with params:"", messageParams); messagebird.messages.create(messageParams, (msgErr, msgResponse) => { if (msgErr) { console.error(""Message Scheduling Error:"", msgErr); // Provide a generic error to the user, log the specific error const errorMessage = ""We couldn't schedule your reminder. Please try again later or contact support.""; return res.redirect(`/?error=${encodeURIComponent(errorMessage)}&name=${encodeURIComponent(name)}&treatment=${encodeURIComponent(treatment)}&number=${encodeURIComponent(number)}&date=${encodeURIComponent(date)}&time=${encodeURIComponent(time)}`); } // Step 5: Store Appointment & Confirm console.log(""Message scheduled successfully:"", msgResponse); // Log contains message ID etc. const appointment = { id: AppointmentDatabase.length + 1, // Simple ID for demo name: name, treatment: treatment, number: recipientNumber, // Store normalized number appointmentDT: appointmentDT.toISOString(), reminderDT: reminderDT.toISOString(), messagebirdMessageId: msgResponse.id // Store MessageBird message ID if needed }; AppointmentDatabase.push(appointment); console.log(""Appointment stored (in-memory):"", appointment); // Render confirmation page res.render('confirm', { appointment }); }); // End messages.create callback }); // End lookup.read callback }); // End POST /book route // --- Basic Error Handling Middleware (Add more robust logging) --- app.use((err, req, res, next) => { console.error(""Unhandled Error:"", err.stack); res.status(500).send('Something broke!'); }); // --- End Error Handling --- // --- Start Server --- app.listen(port, () => { console.log(`Reminder App listening at http://localhost:${port}`); }); // --- End Start Server ---
-
Create Main Layout (
views/layouts/main.handlebars
): This is the main HTML structure.<!-- views/layouts/main.handlebars --> <!DOCTYPE html> <html> <head> <meta charset=""utf-8""> <meta name=""viewport"" content=""width=device-width_ initial-scale=1""> <title>BeautyBird Appointments</title> <!-- Add basic styling or link to CSS framework if desired --> <style> body { font-family: sans-serif; padding: 20px; } .error { color: red; margin-bottom: 15px; } label { display: block; margin-top: 10px; } input, select { width: 100%; padding: 8px; margin-top: 5px; box-sizing: border-box; } button { padding: 10px 15px; margin-top: 15px; cursor: pointer; } </style> </head> <body> <h1>BeautyBird Appointments</h1> <hr> {{{body}}} <!-- Page content will be injected here --> <hr> <footer> <p>© BeautyBird Demo</p> </footer> </body> </html>
-
Create Booking Form (
views/home.handlebars
): The form users will interact with.<!-- views/home.handlebars --> <h2>Book Your Appointment</h2> {{#if error}} <p class=""error"">{{error}}</p> {{/if}} <form action=""/book"" method=""POST""> <div> <label for=""name"">Name:</label> <input type=""text"" id=""name"" name=""name"" value=""{{name}}"" required> </div> <div> <label for=""treatment"">Treatment:</label> <select id=""treatment"" name=""treatment"" required> <option value="""">-- Select Treatment --</option> <option value=""Haircut"" {{#if (eq treatment ""Haircut"")}}selected{{/if}}>Haircut</option> <option value=""Manicure"" {{#if (eq treatment ""Manicure"")}}selected{{/if}}>Manicure</option> <option value=""Pedicure"" {{#if (eq treatment ""Pedicure"")}}selected{{/if}}>Pedicure</option> <option value=""Facial"" {{#if (eq treatment ""Facial"")}}selected{{/if}}>Facial</option> </select> </div> <div> <label for=""number"">Mobile Phone Number (for SMS reminder):</label> <!-- Use type=""tel"" for better mobile usability --> <input type=""tel"" id=""number"" name=""number"" value=""{{number}}"" placeholder=""e.g._ +12025550181"" required> </div> <div> <label for=""date"">Date:</label> <input type=""date"" id=""date"" name=""date"" value=""{{date}}"" required> </div> <div> <label for=""time"">Time:</label> <input type=""time"" id=""time"" name=""time"" value=""{{time}}"" required> </div> <button type=""submit"">Book Appointment & Schedule Reminder</button> </form>
- We use
{{#if error}}
to display errors passed via query parameters on redirect. - We pre-fill form fields with
value=""{{...}}""
if the user is redirected after an error. type=""tel""
is recommended for phone number inputs.- The
eq
helper (defined inindex.js
) is used to re-select the previously chosen treatment on error.
- We use
-
Create Confirmation Page (
views/confirm.handlebars
): Shown after successful booking.<!-- views/confirm.handlebars --> <h2>Appointment Confirmed!</h2> <p>Thank you, {{appointment.name}}!</p> <p>Your appointment for a <strong>{{appointment.treatment}}</strong> is booked for:</p> <p><strong>Date & Time:</strong> {{formatDateTime appointment.appointmentDT 'YYYY-MM-DD HH:mm'}}</p> <p><strong>Mobile Number:</strong> {{appointment.number}}</p> <p>An SMS reminder will be sent to your mobile number approximately 3 hours before your appointment (around {{formatDateTime appointment.reminderDT 'HH:mm'}}).</p> <p><a href="" />Book another appointment</a></p>
- Uses the
formatDateTime
helper defined inindex.js
to display dates nicely.
- Uses the
Why this approach?
- Direct Scheduling: We leverage MessageBird's
scheduledDatetime
parameter. This offloads the scheduling logic to MessageBird, simplifying our application. The alternative involves storing appointments and running a separate background job (usingnode-cron
or similar) to periodically check the database and send messages, which adds complexity but offers more control. - Validation Flow: We validate input sequentially: basic presence check -> date/time validity -> phone number validity (via Lookup API) -> schedule message. This prevents unnecessary API calls if basic validation fails.
- User Feedback: Redirecting with error messages and pre-filled form data provides a better user experience than just showing a generic error page.
3. Building an API layer
This tutorial implements a server-rendered web application, not a separate REST API. The POST /book
endpoint acts as the primary interface for creating appointments and scheduling reminders, driven by HTML form submissions. If a separate API were required (e.g., for a mobile app or single-page application frontend), you would structure the routes to accept and return JSON, implement token-based authentication (like JWT), and potentially separate the route handling logic into controller files.
4. Integrating with MessageBird
Integration involves obtaining credentials, configuring the SDK, and calling the relevant API methods.
-
Obtain API Key:
- Navigate to the MessageBird Dashboard.
- Go to the ""Developers"" section in the left-hand menu.
- Click on ""API access"".
- You can use the default
live
key or create a new one specifically for this application. - Important: Keep your API key secure. Do not hardcode it directly in your source code.
-
Configure SDK (
index.js
): As shown inindex.js
, we load the key from.env
and initialize the SDK:// index.js (snippet) require('dotenv').config(); // ... validation check for API key ... const messagebird = require('messagebird')(process.env.MESSAGEBIRD_API_KEY);
MESSAGEBIRD_API_KEY
: (Required) Your authentication credential. Find this in the MessageBird Dashboard under Developers > API access.COUNTRY_CODE
: (Optional,.env
file) The default ISO 3166-1 alpha-2 country code used by the Lookup API if the user doesn't provide a full international number (e.g.,US
,NL
,GB
). Obtain the correct code for your primary user base.
-
Use Lookup API (
messagebird.lookup.read
):- Called within the
POST /book
route. - Purpose: Validates the phone number format and checks if it's a mobile number capable of receiving SMS. It also returns the number in a standardized international format (
response.phoneNumber
), which is best practice for sending messages. - Parameters:
number
: The phone number string provided by the user.countryCode
: The optional default country code from.env
.callback(err, response)
: Handles the API response. Checkerr
for issues (especiallyerr.errors[0].code === 21
for invalid format) andresponse.type
to ensure it'smobile
.
- Called within the
-
Use Messages API (
messagebird.messages.create
):- Called within the
POST /book
route after successful validation. - Purpose: Schedules the SMS reminder.
- Parameters (Object):
originator
: The sender ID displayed on the recipient's phone. Can be an alphanumeric string (e.g., 'BeautyBird') or a purchased virtual mobile number from MessageBird. Note: Alphanumeric sender IDs have restrictions and are not supported in all countries (e.g., USA, Canada). Check MessageBird's country restrictions or use a virtual number for broader compatibility.recipients
: An array containing the phone number(s) to send to. Use the normalizedresponse.phoneNumber
from the Lookup result.scheduledDatetime
: An ISO 8601 formatted timestamp string specifying when the message should be sent. We usereminderDT.toISOString()
from Moment.js.body
: The text content of the SMS message.callback(err, response)
: Handles the API response. Checkerr
for scheduling failures.response
contains details like the MessageBird message ID upon success.
- Called within the
Fallback Mechanisms: For production, consider adding logic to handle cases where the MessageBird API might be temporarily unavailable (e.g., retry scheduling with exponential backoff, log the failure for manual intervention). However, this basic example focuses on the core scheduling flow.
5. Implementing error handling and logging
Robust error handling and logging are crucial for production systems.
-
Consistent Error Strategy:
- API Errors: Catch errors in the callbacks for
messagebird.lookup.read
andmessagebird.messages.create
. Log the specific error for debugging (console.error
). Provide user-friendly error messages via redirection (/?error=...
). Avoid exposing raw API error details to the user. - Validation Errors: Handle invalid user input (missing fields, invalid date/time) before making API calls. Redirect back to the form with clear error messages.
- Unhandled Errors: Use a final Express error handling middleware (
app.use((err, req, res, next) => {...})
) to catch unexpected server errors, log them, and send a generic 500 response.
- API Errors: Catch errors in the callbacks for
-
Logging:
- The current example uses
console.log
for informational messages andconsole.error
for errors. - Production Recommendation: Replace
console
calls with a dedicated logging library like Winston or Pino. Configure logging levels (e.g., info, warn, error) and output formats (e.g., JSON) suitable for log aggregation systems (like Datadog, Splunk, ELK stack). - Log Key Events: Log successful validations, scheduling attempts (including parameters), successful scheduling (with MessageBird ID), and errors encountered at each step.
- The current example uses
-
Retry Mechanisms (Not Implemented):
- For critical operations like scheduling reminders, if a MessageBird API call fails due to a transient network issue or temporary service disruption (e.g., 5xx errors), you could implement a retry strategy.
- Approach: Use libraries like
async-retry
or implement custom logic usingsetTimeout
with exponential backoff (e.g., wait 1s, then 2s, then 4s) before retrying the API call a limited number of times. Queue failed jobs for later processing if retries fail. This adds significant complexity and is omitted here for simplicity.
Testing Error Scenarios:
- Submit the form with missing fields.
- Enter an invalid date/time (e.g., in the past).
- Enter an invalid phone number format (e.g., ""abcdef"").
- Enter a valid non-mobile number (e.g., a landline if known).
- Temporarily use an invalid
MESSAGEBIRD_API_KEY
in.env
to test authentication errors. - Schedule an appointment very close to the current time (less than 3h 5m away) to trigger the date validation.
6. Creating a database schema and data layer
The provided tutorial uses a simple in-memory array (AppointmentDatabase
) for storing appointments. This is not suitable for production as data will be lost when the server restarts.
For smaller projects or simpler persistence needs, a file-based database like SQLite (using a library like sqlite3
) could be a good intermediate step before moving to a full database server like PostgreSQL or MongoDB.
Production Recommendation: Use a Database Server
A persistent database server (like PostgreSQL, MongoDB, MySQL) is essential for most production applications. Here's a conceptual schema using Mongoose (for MongoDB) as an example:
// models/Appointment.js (Conceptual Example - requires mongoose setup)
const mongoose = require('mongoose');
const appointmentSchema = new mongoose.Schema({
name: { type: String, required: true },
treatment: { type: String, required: true },
// Store the normalized phone number from MessageBird Lookup
phoneNumber: { type: String, required: true, index: true },
appointmentDateTime: { type: Date, required: true, index: true },
reminderDateTime: { type: Date, required: true, index: true },
// Store the MessageBird message ID for tracking/cancellation
messagebirdMessageId: { type: String, index: true },
// Status could be 'scheduled', 'sent', 'failed', 'cancelled'
reminderStatus: { type: String, default: 'scheduled', index: true },
createdAt: { type: Date, default: Date.now }
});
// Add methods if using a cron-based approach, less needed for direct scheduling
// appointmentSchema.statics.findAppointmentsForReminder = function() { ... };
module.exports = mongoose.model('Appointment', appointmentSchema);
Implementation Steps (If using MongoDB/Mongoose):
- Install Mongoose:
npm install mongoose
- Connect to Database: In
index.js
, establish a connection to your MongoDB instance early in the application startup.// index.js (snippet) - Requires MongoDB running const mongoose = require('mongoose'); // ... other requires ... // Replace with your MongoDB connection string (ideally from .env) const mongoURI = process.env.MONGO_URI || 'mongodb://localhost:27017/beautybird'; mongoose.connect(mongoURI) .then(() => console.log('MongoDB connected successfully.')) .catch(err => { console.error('MongoDB connection error:', err); process.exit(1); // Exit if DB connection fails });
- Define Schema: Create the
models/Appointment.js
file as shown above. - Replace In-Memory Storage: Modify the
POST /book
route:- Import the model:
const Appointment = require('./models/Appointment');
- Instead of
AppointmentDatabase.push()
, use Mongoose to save:// Inside messages.create success callback const newAppointment = new Appointment({ name: name, treatment: treatment, phoneNumber: recipientNumber, appointmentDateTime: appointmentDT.toISOString(), reminderDateTime: reminderDT.toISOString(), messagebirdMessageId: msgResponse.id, reminderStatus: 'scheduled' // Initial status }); newAppointment.save() .then(savedAppointment => { console.log(""Appointment saved to DB:"", savedAppointment); res.render('confirm', { appointment: savedAppointment }); // Pass saved data }) .catch(dbErr => { console.error(""Database Save Error:"", dbErr); // Handle DB error - maybe try to cancel the scheduled message? // Render an error page or redirect with error res.status(500).send(""Failed to save appointment after scheduling reminder.""); });
- Import the model:
Data Access: Mongoose provides methods like save()
, find()
, findById()
, findOneAndUpdate()
, etc., for interacting with the database.
7. Adding security features
Security is paramount for any web application.
-
Input Validation & Sanitization:
- Current: Basic checks for field existence.
- Recommendation: Use a library like
express-validator
to define stricter validation rules (e.g., check ifname
is a non-empty string,date
is a valid date format,number
matches a reasonable phone pattern before sending to Lookup). Sanitize inputs to prevent Cross-Site Scripting (XSS) – although Handlebars auto-escapes by default, explicit sanitization adds another layer.
npm install express-validator
// Example using express-validator in POST /book const { body, validationResult } = require('express-validator'); app.post('/book', // Validation rules body('name').trim().notEmpty().escape(), body('treatment').trim().notEmpty().escape(), // Basic format check - adjust 'any' based on expected locales if needed body('number').trim().notEmpty().isMobilePhone('any', { strictMode: false }), body('date').isDate(), body('time').matches(/^([01]\d|2[0-3]):([0-5]\d)$/), // HH:mm format (req, res) => { const errors = validationResult(req); if (!errors.isEmpty()) { console.log(""Validation Errors:"", errors.array()); // Redirect with the first error message and repopulate form data const queryParams = new URLSearchParams({ error: errors.array()[0].msg, name: req.body.name || '', treatment: req.body.treatment || '', number: req.body.number || '', date: req.body.date || '', time: req.body.time || '' }).toString(); return res.redirect(`/?${queryParams}`); } // ... proceed with validated req.body data ... // (rest of the original /book logic goes here, using validated data) } );
Note: The redirect logic in the example above needs to be merged with the existing
/book
route logic. -
Common Vulnerabilities:
- Helmet: Use the
helmet
middleware to set various security-related HTTP headers (likeX-Frame-Options
,Strict-Transport-Security
,Content-Security-Policy
).npm install helmet
// index.js (snippet) const helmet = require('helmet'); // ... other requires ... const app = express(); // Use Helmet middleware early, before routes app.use(helmet()); // ... rest of middleware and routes ...
- Helmet: Use the