In this post, we'll be creating a Slack channel called #finance where we'll send alerts on the transfer of funds and also send a reply to the same message after the transfer has been delivered.
Prerequisite:
- You should be a user of Slack, or have your own slack.com account,
- Create Channels for your notifications
- Create an Alerta account on (app.usealerta.com)
- Create a basic fintech app to plug in our alerts
We are going to use a Sample Fintech app,
Create Slack Channel
On Slack, let's assume we have a #finance team, #marketing team, and #security team.
We'll go ahead and create a channel for each team and add each employee respectively.
After this is done, you should have your channels created and ready to be integrated.
Create Alerta Account
Create an account to get your API key from the Alerta App Here
After successfully creating an account, save your API key, then switch to the integrations
page and select slack
Select the channel you created from the Slack Oauth page. See below
After successful integration, the channel should be integrated
Note: This is how you can add any channels to your alerta account
Create the Sample Fintech App
Now that we have successfully added our channels to the slack workspace.
Let us see how to integrate Alerta into our code base. In case you want o see the full app, See GitHub to skip the step-by-step process
Also, you can watch the Youtube video
Otherwise, let us continue
Installing Nest JS and create a nest app using the following commands
❯ npm i -g @nestjs/cli
❯ nest new sample-fintech-app
❯ cd sample-fintech-app
Let's create the wallet service, module and controller
❯ npm i @nestjs/config @nestjs/axios
❯ nest g module wallet
❯ nest g service wallet
❯ nest g controller wallet
Let's create the alerta service, module and controller
❯ nest g module alerta
❯ nest g service alerta
Folder API Structure
After running all the commands above, your sample finance app which should have this folder structure, feel free to remove the .spec
files.
sample-wallet-app/
├── src/
│ ├── alerta/
│ │ ├── alerta.module.ts # Module for alerta functions
│ │ └── alerta.service.ts # for alerta external api calls
│ ├── wallet/
│ │ ├── wallet.controller.ts # Controller handling wallet
│ │ ├── wallet.module.ts # Module for Wallet functions
│ │ └── wallet.service.ts # handling business logic
│ ├── app.module.ts # Main application module
│ └── main.ts # Entry point for the application
├── node_modules/ # Node dependencies
├── package.json # Dependencies
├── package-lock.json # Lock file extracting version of our dependencies
├── tsconfig.json # TypeScript configuration file
└── .eslintrc.js # ESLint configuration (optional)
Add Wallet Services
We'll create two functions in our wallet.service.ts
file. One to transfer funds and the second to withdraw funds.
import { Injectable } from '@nestjs/common';
import { AlertaService } from 'src/alerta/alerta.service';
@Injectable()
export class WalletService {
constructor(private readonly alertaService: AlertaService) {}
async walletTransfer(transferDto: {
fromWalletID: string;
toWalletID: string;
amount: number;
}) {
const { fromWalletID, toWalletID, amount } = transferDto;
const message = `Transferred ${amount} from wallet ${fromWalletID} to wallet ${toWalletID}`;
return { message, success: true };
}
withdrawToBank(withDrawDto: {
walletID: string;
bankAccount: string;
amount: number;
}) {
const { walletID, bankAccount, amount } = withDrawDto;
const message = `Withdrawn ${amount} from wallet ${walletID} to bank account ${bankAccount}`;
return { message, success: true };
}
}
Add Wallet Controller
We'll create two functions in our wallet.controller.ts
file. One initiates the transfer of funds and the second to withdraws funds.
import { Body, Controller, Post } from '@nestjs/common';
import { WalletService } from './wallet.service';
@Controller('wallet')
export class WalletController {
constructor(private readonly walletService: WalletService) {}
@Post('transfer')
walletTransfer(
@Body()
transferDto: {
fromWalletID: string;
toWalletID: string;
amount: number;
},
) {
return this.walletService.walletTransfer(transferDto);
}
@Post('withdraw')
withdrawToBank(
@Body()
withDrawDto: {
walletID: string;
bankAccount: string;
amount: number;
},
) {
return this.walletService.withdrawToBank(withDrawDto);
}
}
Now that we have created our service and controller for the wallet service, lets add the module
for wallet.module.ts
import { Module } from '@nestjs/common';
import { WalletController } from './wallet.controller';
import { WalletService } from './wallet.service';
@Module({
controllers: [WalletController],
providers: [WalletService],
})
export class WalletModule {}
Add Env Variables
To secure our API keys, we'll add a .env
file and specify the api key and url there
ALERTA_URL=place your api url
ALERTA_KEY=place your api key here
Add Alerta Service
After setting up our wallet and our env file, we'll create two functions in our alerta.service.ts
file. One to send the message and the second to reply to messages.
import { HttpService } from '@nestjs/axios';
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { catchError, lastValueFrom } from 'rxjs';
@Injectable()
export class AlertaService {
private alertaUrl = this.config.get('ALERTA_URL');
private alertaKey = this.config.get('ALERTA_KEY');
constructor(
private config: ConfigService,
private httpService: HttpService,
) {}
async sendAlert(alertaDto: {
message: string;
channel: string;
replyTo: boolean;
}): Promise<any> {
const { message, channel, replyTo } = alertaDto;
try {
const headers = {
secretKey: `secret ${this.alertaKey}`,
'Content-Type': 'application/json',
};
const res = await lastValueFrom(
this.httpService
.post(
`${this.alertaUrl}/send`,
{ message, channel, replyTo },
{
headers,
},
)
.pipe(
catchError((error) => {
throw new HttpException(
`Error fetching data from external API: ${error.message}`,
HttpStatus.BAD_REQUEST,
);
}),
),
);
return res.data;
return res.data;
} catch (error) {
return error;
}
}
async replyAlert(alertaDto: {
channelId: string;
threadId: string;
channelRef: string;
message: string;
}): Promise<any> {
const { channelId, threadId, channelRef, message } = alertaDto;
try {
const headers = {
secretKey: `secret ${this.alertaKey}`,
'Content-Type': 'application/json',
};
const res = await lastValueFrom(
this.httpService
.post(
`${this.alertaUrl}/reply`,
{ channelId, threadId, channelRef, message },
{ headers },
)
.pipe(
catchError((error) => {
throw new HttpException(
`Error processing data from API ${error}`,
HttpStatus.BAD_REQUEST,
);
}),
),
);
return res.data;
} catch (error) {}
}
}
Add Alerta Module
After setting up our alerta service, we'll update our module file alerta.module.ts
file.
import { HttpModule } from '@nestjs/axios';
import { Module } from '@nestjs/common';
import { AlertaService } from './alerta.service';
import { ConfigService } from '@nestjs/config';
@Module({
imports: [HttpModule.register({ timeout: 5000, maxRedirects: 5 })],
providers: [AlertaService, ConfigService],
exports: [AlertaService],
})
export class AlertaModule {}
We are almost there,
Before we test, lets us add the alerts to some places in our wallet service so we get the alerts when there is an operation.
First lets update the wallet.module.ts
file to know our Alerta service
.
.
.
import { AlertaModule } from 'src/alerta/alerta.module';
@Module({
imports: [AlertaModule],
controllers: [WalletController],
providers: [WalletService],
})
export class WalletModule {}
also, let us update the app.module.ts
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { WalletService } from './wallet/wallet.service';
import { WalletModule } from './wallet/wallet.module';
import { WalletController } from './wallet/wallet.controller';
import { AlertaService } from './alerta/alerta.service';
import { AlertaModule } from './alerta/alerta.module';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { HttpModule } from '@nestjs/axios';
@Module({
imports: [
ConfigModule.forRoot({ isGlobal: true }),
WalletModule,
AlertaModule,
HttpModule,
],
controllers: [AppController],
providers: [AppService, ConfigService],
})
export class AppModule {}
You will notice that we added the ConfigModule.forRoot({ isGlobal: true })
this will ensure that all env config is availble and can be used app-wide.
Plug in Alerta into the codebase
Now, in the wallet.service.ts
, let us assume we want to know when any user does a transfer or does a withdraw, we can send a message to the #finance
channel that we created and integrated earlier.
Lets update our code.
import { Injectable } from '@nestjs/common';
import { AlertaService } from 'src/alerta/alerta.service';
@Injectable()
export class WalletService {
// bring the service in by adding it the contructor
constructor(private readonly alertaService: AlertaService) {}
async walletTransfer(transferDto: {
fromWalletID: string;
toWalletID: string;
amount: number;
}) {
const { fromWalletID, toWalletID, amount } = transferDto;
const message = `Transferred ${amount} from wallet ${fromWalletID} to wallet ${toWalletID}`;
// Add the alert from the alerta service here. Also, because we want
// to reply to it later, we'll set `replyTo` to true
await this.alertaService.sendAlert({
message,
channel: 'finance',
replyTo: true,
});
return { message, success: true };
}
.
.
.
}
Now we can send messages to Slack in the channel. Let's assume we want to reply to the same message that was sent earlier.
Usually, you can save the data from the response after you have sent a message so the message can be replied to in the future.
The data needed for the reply is threadId
, channelId
and channelRef
To add the Reply
Before adding the reply feature, we have to mention the @Alerta App in the channel
import { Injectable } from '@nestjs/common';
import { AlertaService } from 'src/alerta/alerta.service';
@Injectable()
export class WalletService {
constructor(private readonly alertaService: AlertaService) {}
async walletTransfer(transferDto: {
fromWalletID: string;
toWalletID: string;
amount: number;
}) {
const { fromWalletID, toWalletID, amount } = transferDto;
const message = `Transferred ${amount} from wallet ${fromWalletID} to wallet ${toWalletID}`;
const alertRes = await this.alertaService.sendAlert({
message,
channel: 'finance',
replyTo: true,
});
// triggering the reply
setTimeout(() => {
this.alertaService.replyAlert({
message: 'Transfer delivered!',
threadId: alertRes.data.replyData.threadId,
channelId: alertRes.data.replyData.channelId,
channelRef: alertRes.data.replyData.channelRef,
});
}, 5000);
return { message, success: true };
}
withdrawToBank(withDrawDto: {
walletID: string;
bankAccount: string;
amount: number;
}) {
const { walletID, bankAccount, amount } = withDrawDto;
const message = `Withdrawn ${amount} from wallet ${walletID} to bank account ${bankAccount}`;
return { message, success: true };
}
}
Now let's test our implementation
Test transfer
The Slack Message
The Reply to the existing message
In conclusion, we created a Slack channel called #finance where we'll send updates messages on the transfer of funds and also send a reply to the same message after the transfer has been delivered.
Thank you.
Top comments (1)
Impressive