This guide provides a step-by-step walkthrough for creating a production-ready SMS appointment scheduling and reminder system using the NestJS framework, Node.js, and the MessageBird API. We will cover everything from initial project setup to deployment and monitoring.
Project Overview and Goals
We aim to build a backend system that enables users (or other systems) to schedule appointments and automatically triggers SMS reminders via MessageBird a set amount of time before the appointment.
Problem Solved: Reduces no-shows for appointments by sending timely SMS reminders, improving operational efficiency and customer experience. Automates the reminder process, saving manual effort.
Technologies:
- Node.js: The runtime environment for our backend application.
- NestJS: A progressive Node.js framework for building efficient, reliable, and scalable server-side applications. Its modular architecture, dependency injection, and TypeScript support make it ideal for robust applications.
- MessageBird: The communications platform used for sending SMS messages and validating phone numbers.
- TypeScript: Enhances JavaScript with static typing for better code quality and maintainability.
- PostgreSQL (Optional but Recommended): A powerful open-source relational database for persisting appointment data. We'll use TypeORM as the Object-Relational Mapper (ORM).
- Docker: For containerizing the application for consistent development and deployment environments.
System Flow:
- A client sends a request to the NestJS API endpoint to schedule an appointment.
- The Controller receives the request, validating the input using Data Transfer Objects (DTOs).
- The Controller calls the
AppointmentService
to handle the business logic. - The
AppointmentService
validates the phone number using MessageBird Lookup. - The
AppointmentService
(optionally) persists appointment details to the database (e.g., PostgreSQL via TypeORM). - The
AppointmentService
uses the MessageBird SDK to schedule the SMS reminder via the MessageBird API. - MessageBird sends the SMS at the scheduled time.
Prerequisites:
- Node.js (LTS version recommended, e.g., v18 or v20) and npm/yarn installed.
- A MessageBird account with API credentials (API Key).
- Basic understanding of TypeScript and REST APIs.
- Access to a terminal or command prompt.
- (Optional) Docker and Docker Compose installed.
- (Optional) PostgreSQL database running locally or accessible.
Final Outcome: A robust NestJS application with an API endpoint to schedule appointments, validate phone numbers, store appointment data, and reliably schedule SMS reminders via MessageBird.
1. Setting up the Project
Let's initialize our NestJS project and install the necessary dependencies.
-
Install NestJS CLI: If you don't have the NestJS CLI installed globally, run:
npm install -g @nestjs/cli
-
Create New NestJS Project:
nest new nestjs-messagebird-reminders cd nestjs-messagebird-reminders
Choose your preferred package manager (npm or yarn) when prompted.
-
Install Dependencies: We need modules for configuration, validation, interacting with MessageBird, and database operations (if using one).
# Core dependencies npm install @nestjs/config class-validator class-transformer dotenv # MessageBird SDK npm install messagebird # Date handling npm install date-fns # Database (Optional: PostgreSQL + TypeORM) npm install @nestjs/typeorm typeorm pg # Optional: For complex *internal* application scheduling tasks (e.g., background jobs, not the SMS scheduling itself) # npm install @nestjs/schedule
@nestjs/config
: Manages environment variables.class-validator
,class-transformer
: For validating request DTOs.dotenv
: Loads environment variables from a.env
file.messagebird
: The official Node.js SDK for the MessageBird API.date-fns
: Modern library for date manipulation.@nestjs/typeorm
,typeorm
,pg
: For PostgreSQL integration via TypeORM.@nestjs/schedule
: Useful for internal application scheduling, distinct from MessageBird's SMS scheduling.
-
Environment Configuration (
.env
): Create a.env
file in the project root:# .env PORT=3000 # MessageBird Credentials & Settings MESSAGEBIRD_API_KEY=YOUR_LIVE_OR_TEST_API_KEY MESSAGEBIRD_ORIGINATOR=YourAppName # Or your MessageBird phone number MESSAGEBIRD_LOOKUP_COUNTRY_CODE=US # Default country code for Lookup API (ISO 3166-1 alpha-2) REMINDER_HOURS_BEFORE=3 # How many hours before appointment to send reminder # Database (Optional - Example for PostgreSQL) DB_HOST=localhost DB_PORT=5432 DB_USERNAME=postgres DB_PASSWORD=your_db_password DB_DATABASE=reminders
MESSAGEBIRD_API_KEY
: Obtain this from your MessageBird Dashboard (Developers -> API access -> Show key). Use a live key for real messages or a test key for development without sending actual SMS.MESSAGEBIRD_ORIGINATOR
: The sender ID displayed on the recipient's phone. This can be an alphanumeric string (max 11 chars, country restrictions apply) or a purchased MessageBird virtual mobile number (required for some countries like the US). Check MessageBird documentation for restrictions.MESSAGEBIRD_LOOKUP_COUNTRY_CODE
: Helps the Lookup API parse numbers entered without a country code.REMINDER_HOURS_BEFORE
: Configures the reminder timing.- Database Variables: Adjust these if you are using a database.
-
Load Configuration (
app.module.ts
): Configure theConfigModule
to load the.env
file.// src/app.module.ts import { Module } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; import { AppController } from './app.controller'; import { AppService } from './app.service'; import { AppointmentModule } from './appointment/appointment.module'; // Import TypeOrmModule if using a database (Section 6) // import { TypeOrmModule } from '@nestjs/typeorm'; // Import HealthModule if using health checks (Section 10) // import { HealthModule } from './health/health.module'; @Module({ imports: [ ConfigModule.forRoot({ isGlobal: true, // Make config available globally envFilePath: '.env', }), // Add TypeOrmModule configuration here if using a database (Section 6) AppointmentModule, // We will create this module next // Add HealthModule here if implementing health checks (Section 10) ], controllers: [AppController], providers: [AppService], }) export class AppModule {}
-
Enable Validation Pipe (
main.ts
): Enable the global validation pipe to automatically validate incoming request bodies based on DTOs.// src/main.ts import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import { ValidationPipe } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; async function bootstrap() { const app = await NestFactory.create(AppModule); const configService = app.get(ConfigService); const port = configService.get<number>('PORT') || 3000; app.useGlobalPipes(new ValidationPipe({ whitelist: true, // Strip properties not defined in DTO transform: true, // Automatically transform payloads to DTO instances forbidNonWhitelisted: true, // Throw error if non-whitelisted properties are present })); await app.listen(port); console.log(`Application is running on: ${await app.getUrl()}`); } bootstrap();
-
Project Structure: NestJS encourages a modular structure. We'll create an
AppointmentModule
to encapsulate all appointment-related logic.nest g module appointment nest g controller appointment --no-spec # Skip spec files for brevity nest g service appointment --no-spec
This creates:
src/appointment/appointment.module.ts
src/appointment/appointment.controller.ts
src/appointment/appointment.service.ts
Create a DTO directory:
mkdir src/appointment/dto
2. Implementing Core Functionality (Scheduling Logic)
Now, let's implement the service responsible for validating data, interacting with MessageBird, and (optionally) saving to the database.
-
Define Appointment DTO (
create-appointment.dto.ts
): Create a Data Transfer Object to define the expected shape and validation rules for the incoming request body.// src/appointment/dto/create-appointment.dto.ts import { IsString, IsNotEmpty, IsPhoneNumber, IsDateString, MinLength, IsISO8601, } from 'class-validator'; export class CreateAppointmentDto { @IsString() @IsNotEmpty() @MinLength(2) customerName: string; // Basic validation via @IsPhoneNumber. // WARNING: For robust production validation (strict E.164, international formats), // this might be insufficient. Strongly consider using 'google-libphonenumber' // wrapped in a custom NestJS validator decorator. @IsPhoneNumber(null) // Pass region code (e.g., 'US') if needed for non-E.164 numbers @IsNotEmpty() phoneNumber: string; // Expecting E.164 format ideally (e.g., +14155552671) @IsString() @IsNotEmpty() treatment: string; @IsISO8601({ strict: true }, { message: 'appointmentTime must be a valid ISO 8601 date string (e.g., 2025-12-31T14:30:00.000Z)'}) @IsNotEmpty() appointmentTime: string; // Expecting ISO 8601 format UTC (e.g., '2025-12-31T14:30:00.000Z') }
- We use
class-validator
decorators to enforce rules. @IsPhoneNumber
: Provides basic validation. See warning in the code comment about production readiness.@IsISO8601
: Ensures the date is in the correct format. Using UTC is crucial for avoiding timezone issues.
- We use
-
Implement
AppointmentService
: This service will contain the core logic. Note: This version does not include database persistence (see Section 6 for the optional DB integration).// src/appointment/appointment.service.ts import { Injectable, Logger, BadRequestException, InternalServerErrorException } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import * as MessageBird from 'messagebird'; // Import MessageBird SDK import { CreateAppointmentDto } from './dto/create-appointment.dto'; import { subHours, isBefore, parseISO, formatISO } from 'date-fns'; // Modern date library // Define types for MessageBird client and responses (optional but good practice) type MessageBirdClient = ReturnType<typeof MessageBird>; // Define interfaces based on actual MessageBird SDK response structures if needed @Injectable() export class AppointmentService { private readonly logger = new Logger(AppointmentService.name); private messagebird: MessageBirdClient; private readonly apiKey: string; private readonly originator: string; private readonly countryCode: string; private readonly reminderHoursBefore: number; constructor(private configService: ConfigService) { this.apiKey = this.configService.get<string>('MESSAGEBIRD_API_KEY'); this.originator = this.configService.get<string>('MESSAGEBIRD_ORIGINATOR'); this.countryCode = this.configService.get<string>('MESSAGEBIRD_LOOKUP_COUNTRY_CODE'); // Read reminder hours from config, default to 3 if not set this.reminderHoursBefore = parseInt(this.configService.get<string>('REMINDER_HOURS_BEFORE', '3'), 10); if (!this.apiKey || !this.originator) { this.logger.error('MessageBird API Key or Originator not configured!'); throw new InternalServerErrorException('Messaging service configuration error.'); } if (isNaN(this.reminderHoursBefore) || this.reminderHoursBefore <= 0) { this.logger.warn(`Invalid REMINDER_HOURS_BEFORE value. Defaulting to 3.`); this.reminderHoursBefore = 3; } // Initialize MessageBird client this.messagebird = MessageBird(this.apiKey); } /** * Schedules an SMS reminder for an appointment. * This version does NOT persist to a database. See Section 6 for DB integration. */ async scheduleAppointmentReminder(dto: CreateAppointmentDto): Promise<{ id: string; message: string }> { this.logger.log(`Scheduling reminder for ${dto.customerName} at ${dto.appointmentTime}`); const appointmentDateTime = parseISO(dto.appointmentTime); const reminderDateTime = subHours(appointmentDateTime, this.reminderHoursBefore); const now = new Date(); // 1. Validate Appointment Time const minReminderTime = new Date(now.getTime() + 5 * 60000); // 5 minutes from now buffer if (isBefore(reminderDateTime, minReminderTime)) { this.logger.warn(`Failed scheduling: Reminder time ${formatISO(reminderDateTime)} is too soon.`); throw new BadRequestException(`Appointment must be scheduled far enough in advance to allow for the ${this.reminderHoursBefore}-hour reminder (at least 5 minutes from now).`); } // 2. Validate Phone Number via MessageBird Lookup let validatedPhoneNumber: string; try { validatedPhoneNumber = await new Promise<string>((resolve, reject) => { this.messagebird.lookup.read(dto.phoneNumber, this.countryCode, (err: any, response: any) => { if (err) { this.logger.error(`Lookup failed for partial number: ${err.errors?.[0]?.description || err.message}`, err.stack); if (err.errors?.[0]?.code === 21) { return reject(new BadRequestException('Invalid phone number format provided.')); } return reject(new InternalServerErrorException('Failed to validate phone number.')); } if (response.type !== 'mobile') { this.logger.warn(`Lookup successful for partial number, but type is '${response.type}'.`); return reject(new BadRequestException('The provided phone number must be a mobile number.')); } this.logger.log(`Lookup successful. Normalized number format obtained.`); resolve(response.phoneNumber); // Use the normalized number }); }); } catch (error) { throw error; // Re-throw exceptions from the promise reject calls } // 3. Schedule the SMS with MessageBird const reminderBody = `${dto.customerName}, here's a reminder for your ${dto.treatment} appointment scheduled for ${formatISO(appointmentDateTime)}. See you soon!`; const params = { originator: this.originator, recipients: [validatedPhoneNumber], scheduledDatetime: formatISO(reminderDateTime), // Use ISO 8601 format body: reminderBody, }; try { const messageResponse = await new Promise<any>((resolve, reject) => { this.messagebird.messages.create(params, (err: any, response: any) => { if (err) { this.logger.error(`Failed to schedule SMS: ${err.errors?.[0]?.description || err.message}`, err.stack); return reject(new InternalServerErrorException('Failed to schedule SMS reminder.')); } this.logger.log(`SMS scheduled successfully. Message ID: ${response.id}`); resolve(response); }); }); // 4. (Optional) Persist Appointment to Database (See Section 6) // If implementing Section 6, the database save logic goes here. // 5. Return Success Response (without appointmentId for non-DB version) return { id: messageResponse.id, // MessageBird message ID message: `Appointment reminder scheduled successfully for ${dto.customerName}.`, }; } catch (error) { this.logger.error(`Error during SMS scheduling: ${error.message}`, error.stack); throw new InternalServerErrorException('An error occurred while scheduling the appointment reminder.'); } } }
- Initialization: Reads config (including
REMINDER_HOURS_BEFORE
) and initializes MessageBird. - Date Handling: Uses
date-fns
for parsing, calculation (subHours
), and validation (isBefore
). - Lookup: Calls
messagebird.lookup.read
wrapped in aPromise
, handles errors, checks formobile
type. - Scheduling: Calls
messagebird.messages.create
withscheduledDatetime
, wrapped in aPromise
. - Return Type: Returns
Promise<{ id: string; message: string }>
as database persistence is not included here. - Error Handling: Uses NestJS exceptions and
Logger
.
- Initialization: Reads config (including
3. Building the API Layer
Create the controller to expose an endpoint for scheduling appointments.
// src/appointment/appointment.controller.ts
import { Controller, Post, Body, HttpCode, HttpStatus, Logger } from '@nestjs/common';
import { AppointmentService } from './appointment.service';
import { CreateAppointmentDto } from './dto/create-appointment.dto';
@Controller('appointments')
export class AppointmentController {
private readonly logger = new Logger(AppointmentController.name);
constructor(private readonly appointmentService: AppointmentService) {}
@Post('/schedule')
@HttpCode(HttpStatus.CREATED)
async scheduleReminder(@Body() createAppointmentDto: CreateAppointmentDto) {
this.logger.log(`Received request to schedule reminder for ${createAppointmentDto.customerName}`);
// ValidationPipe in main.ts handles DTO validation
// The return type here will match the AppointmentService return type
// (either with or without appointmentId depending on DB integration)
return this.appointmentService.scheduleAppointmentReminder(createAppointmentDto);
}
}
- Defines a POST endpoint at
/appointments/schedule
. - Injects the validated
CreateAppointmentDto
using@Body()
. - Sets the success status code to 201 Created.
- Delegates the core logic to the injected
AppointmentService
.
Testing the Endpoint (Example using curl):
Assuming the app is running on port 3000:
curl -X POST http://localhost:3000/appointments/schedule \
-H ""Content-Type: application/json"" \
-d '{
""customerName"": ""Jane Doe"",
""phoneNumber"": ""+12025550154"",
""treatment"": ""Consultation"",
""appointmentTime"": ""2025-12-25T15:00:00.000Z""
}'
Note: Use a real mobile number for testing with live keys. The appointmentTime
must be later than the configured reminder hours plus a small buffer (e.g., 5 minutes) from the current time, specified in UTC.
Expected Success Response (201 Created - without DB):
{
""id"": ""mb_message_id_string"",
""message"": ""Appointment reminder scheduled successfully for Jane Doe.""
}
Expected Error Response (400 Bad Request - e.g., invalid date):
{
""statusCode"": 400,
""message"": [
""appointmentTime must be a valid ISO 8601 date string (e.g., 2025-12-31T14:30:00.000Z)""
],
""error"": ""Bad Request""
}
4. Integrating with MessageBird (Deep Dive)
We've already initialized the client and used the Lookup and Messages APIs. Let's recap key integration points:
- SDK Initialization: Done in
AppointmentService
constructor usingMessageBird(this.apiKey)
. - API Key Security: Handled via
.env
file andConfigModule
. Never commit your API key to version control. Use environment variables in deployment environments. - Dashboard Configuration:
- API Key: Navigate to MessageBird Dashboard -> Developers -> API access (REST). Create a new key (choose ""Live"" or ""Test"") or copy an existing one. Store it securely.
- Required Permissions: Ensure the API key has permissions for ""Lookup API access"" and ""Send SMS via API"" (or similar wording in the dashboard).
- Originator: Consider purchasing a Virtual Mobile Number (VMN) from MessageBird (Numbers section) for better deliverability, especially in countries like the US/Canada that restrict alphanumeric senders. Configure this number or your chosen alphanumeric ID in
MESSAGEBIRD_ORIGINATOR
. - Lookup: The
MESSAGEBIRD_LOOKUP_COUNTRY_CODE
in.env
aids parsing numbers without a country code. - Scheduled Messages: View scheduled (and sent) messages in the MessageBird Dashboard -> SMS -> Scheduled overview.
- API Key: Navigate to MessageBird Dashboard -> Developers -> API access (REST). Create a new key (choose ""Live"" or ""Test"") or copy an existing one. Store it securely.
- Environment Variables: (Defined in Section 1)
MESSAGEBIRD_API_KEY
: Your secret key.MESSAGEBIRD_ORIGINATOR
: Sender ID.MESSAGEBIRD_LOOKUP_COUNTRY_CODE
: Default country for Lookup.REMINDER_HOURS_BEFORE
: Reminder timing configuration.
- Fallback Mechanisms: The current implementation relies heavily on MessageBird. For critical systems, consider:
- Retries: Implement retries with exponential backoff for transient network errors (see Section 5).
- Monitoring & Alerting: Set up alerts for high failure rates (see Section 10).
- Secondary Provider (Advanced): Integrate a second SMS provider as a fallback (adds complexity).
5. Error Handling, Logging, and Retry Mechanisms
- Error Handling Strategy:
- Use standard NestJS HTTP exceptions (
BadRequestException
,InternalServerErrorException
, etc.) thrown from the service layer. - Validate input rigorously using DTOs and
ValidationPipe
. - Catch specific MessageBird SDK errors (e.g., code 21 for invalid number) and map them appropriately.
- Log detailed errors server-side.
- Use standard NestJS HTTP exceptions (
- Logging:
- Use the built-in
Logger
service (@nestjs/common
). - Log key events: request received, validation results, MessageBird API calls (start, success, failure), database operations (if used), final outcome.
- Include contextual information (e.g., customer name initial, appointment time).
- Security: Be extremely cautious about logging sensitive data. Do not log full phone numbers or API keys unless absolutely necessary and measures like masking or encryption are in place. Log partial identifiers or correlation IDs instead.
- Consider structured logging (JSON) for production environments.
- Use the built-in
- Retry Mechanisms:
- Lookup/Scheduling: For transient network issues, wrap the
messagebird.lookup.read
andmessagebird.messages.create
promise calls with a retry mechanism. Use libraries likeasync-retry
or implement a simple loop with exponential backoff and jitter. Limit retry attempts.// Conceptual example using async-retry (install `npm i async-retry @types/async-retry`) import * as retry from 'async-retry'; // ... inside scheduleAppointmentReminder, wrap the Promise creation for lookup: try { validatedPhoneNumber = await retry(async (bail, attempt) => { this.logger.log(`Lookup attempt ${attempt} for partial number...`); return new Promise<string>((resolve, reject) => { this.messagebird.lookup.read(dto.phoneNumber, this.countryCode, (err: any, response: any) => { if (err) { if (err.errors?.[0]?.code === 21) { // Non-retryable validation error bail(new BadRequestException('Invalid phone number format provided.')); return; } // For other errors, reject to trigger retry return reject(err); // Rejects the promise, retry() catches it } if (response.type !== 'mobile') { // Non-retryable validation error bail(new BadRequestException('The provided phone number must be a mobile number.')); return; } resolve(response.phoneNumber); }); }); }, { retries: 3, factor: 2, minTimeout: 1000, onRetry: (error, attempt) => { this.logger.warn(`Lookup attempt ${attempt} failed. Retrying... Error: ${error.message}`); } }); } catch (error) { // Handle final error after retries or non-retryable errors (from bail) this.logger.error(`Lookup failed permanently for partial number: ${error.message}`, error.stack); if (error instanceof BadRequestException) throw error; // Propagate client errors throw new InternalServerErrorException('Failed to validate phone number after retries.'); } // Apply similar retry logic around messagebird.messages.create if needed
- Database: TypeORM handles some connection retries. Configure the connection pool robustly.
- Lookup/Scheduling: For transient network issues, wrap the
6. Database Schema and Data Layer (Optional: PostgreSQL + TypeORM)
This section details how to add database persistence using TypeORM and PostgreSQL. If you implement this, you need to modify the AppointmentService
and AppModule
shown in previous sections.
-
Install Dependencies: (Should be done in Section 1)
# npm install @nestjs/typeorm typeorm pg
-
Configure
TypeOrmModule
(app.module.ts
): Modify yoursrc/app.module.ts
to includeTypeOrmModule
.// src/app.module.ts (Modified for Database) import { Module } from '@nestjs/common'; import { ConfigModule, ConfigService } from '@nestjs/config'; import { TypeOrmModule } from '@nestjs/typeorm'; // Import TypeOrmModule import { AppController } from './app.controller'; import { AppService } from './app.service'; import { AppointmentModule } from './appointment/appointment.module'; import { Appointment } from './appointment/entities/appointment.entity'; // Import entity // Import HealthModule if using health checks (Section 10) @Module({ imports: [ ConfigModule.forRoot({ /* ... */ }), TypeOrmModule.forRootAsync({ // Configure TypeORM imports: [ConfigModule], inject: [ConfigService], useFactory: (configService: ConfigService) => ({ type: 'postgres', host: configService.get<string>('DB_HOST'), port: configService.get<number>('DB_PORT'), username: configService.get<string>('DB_USERNAME'), password: configService.get<string>('DB_PASSWORD'), database: configService.get<string>('DB_DATABASE'), entities: [Appointment], // Register entity synchronize: configService.get<string>('NODE_ENV') !== 'production', // Dev only! Use migrations in prod. // migrations: [__dirname + '/../db/migrations/*{.ts,.js}'], // For production // cli: { migrationsDir: 'src/db/migrations' } // For production }), }), AppointmentModule, // HealthModule (if used) ], controllers: [AppController], providers: [AppService], }) export class AppModule {}
synchronize: true
is for development only. Use migrations in production.
-
Define
Appointment
Entity: Create the entity file:mkdir src/appointment/entities
// src/appointment/entities/appointment.entity.ts import { Entity, PrimaryGeneratedColumn, Column, Index, CreateDateColumn, UpdateDateColumn } from 'typeorm'; export enum AppointmentStatus { SCHEDULED = 'SCHEDULED', SENT = 'SENT', // Status could be updated via MessageBird webhooks FAILED = 'FAILED', // Status could be updated via MessageBird webhooks CANCELLED = 'CANCELLED', } @Entity('appointments') // Maps to 'appointments' table export class Appointment { @PrimaryGeneratedColumn('uuid') id: string; @Column({ length: 100 }) customerName: string; @Index() @Column({ length: 20 }) // E.164 max length ~15, add buffer phoneNumber: string; // Store the validated, normalized number @Column({ length: 100 }) treatment: string; @Index() @Column({ type: 'timestamp with time zone' }) // Use TIMESTAMPTZ for PostgreSQL appointmentTime: Date; @Column({ type: 'timestamp with time zone' }) reminderTime: Date; @Column({ nullable: true, length: 64 }) // Store MessageBird's message ID messagebirdMessageId?: string; @Index() @Column({ type: 'enum', enum: AppointmentStatus, default: AppointmentStatus.SCHEDULED, }) status: AppointmentStatus; @CreateDateColumn({ type: 'timestamp with time zone' }) createdAt: Date; @UpdateDateColumn({ type: 'timestamp with time zone' }) updatedAt: Date; }
-
Inject Repository and Update Service:
-
Import
TypeOrmModule
inAppointmentModule
:// src/appointment/appointment.module.ts (Modified for Database) import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; // Import import { AppointmentController } from './appointment.controller'; import { AppointmentService } from './appointment.service'; import { Appointment } from './entities/appointment.entity'; // Import entity @Module({ imports: [TypeOrmModule.forFeature([Appointment])], // Make Appointment repository available controllers: [AppointmentController], providers: [AppointmentService], }) export class AppointmentModule {}
-
Inject Repository and Modify
AppointmentService
: Updatesrc/appointment/appointment.service.ts
to inject the repository and save the appointment. ThescheduleAppointmentReminder
method needs changes.// src/appointment/appointment.service.ts (Modified for Database) import { Injectable, Logger, BadRequestException, InternalServerErrorException } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import * as MessageBird from 'messagebird'; import { CreateAppointmentDto } from './dto/create-appointment.dto'; import { subHours, isBefore, parseISO, formatISO } from 'date-fns'; import { InjectRepository } from '@nestjs/typeorm'; // Import import { Repository } from 'typeorm'; // Import import { Appointment, AppointmentStatus } from './entities/appointment.entity'; // Import type MessageBirdClient = ReturnType<typeof MessageBird>; @Injectable() export class AppointmentService { private readonly logger = new Logger(AppointmentService.name); private messagebird: MessageBirdClient; private readonly apiKey: string; private readonly originator: string; private readonly countryCode: string; private readonly reminderHoursBefore: number; constructor( private configService: ConfigService, @InjectRepository(Appointment) // Inject Appointment repository private appointmentRepository: Repository<Appointment>, ) { this.apiKey = this.configService.get<string>('MESSAGEBIRD_API_KEY'); this.originator = this.configService.get<string>('MESSAGEBIRD_ORIGINATOR'); // ... (rest of constructor as before) ... this.reminderHoursBefore = parseInt(this.configService.get<string>('REMINDER_HOURS_BEFORE', '3'), 10); // ... (config checks and MessageBird initialization as before) ... this.messagebird = MessageBird(this.apiKey); } /** * Schedules reminder AND saves appointment to database * (Note: The original provided text was incomplete here. This includes the provided part.) */ // ... (The rest of the modified scheduleAppointmentReminder method would go here) ... }
-