Use handlebars template in nest js application - nestjs

I want to use handle bars template in my nest js application:
<!--confirmation.hbs-->
<p>Hello template</p>
This file is located in src/mail/templates/confirmation.hbs. Also i try to send this template as email:
//mail service
#Injectable()
export class EmailService {
constructor(private readonly mailerService: MailerService) {}
public example(): void {
this.mailerService
.sendMail({
to: 'mail', // list of receivers
from: 'test#nestjs.com', // sender address
subject: 'Testing Nest MailerModule ✔', // Subject line
template: './confirmation',
})
.then((r) => {
console.log(r, 'email is sent');
})
.catch((e) => {
console.log(e, 'error sending email');
});
}
}
My app.module.ts looks:
#Module({
imports: [
MailerModule.forRoot({
transport: {
service: 'Gmail',
auth: {
user: '---secret',
pass: '---secret',
},
},
defaults: {
from: '"No Reply" <no-reply#localhost>',
},
template: {
dir: __dirname + '/templates',
adapter: new HandlebarsAdapter(),
options: {
strict: true,
},
},
}),
TypeOrmModule.forRoot({
type: 'postgres',
host: 'localhost',
port: 5433,
username: '-',
password: '-',
database: '-',
entities: [RegisterEntity],
synchronize: true,
}),
AuthenticationModule,
],
controllers: [AppController, AuthenticationController],
providers: [AppService, AuthenticationService],
})
This is my main.ts
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.setGlobalPrefix(CONSTANTS.GLOBAL_PREFIX);
await app.listen(3000);
}
bootstrap();
This is my nest-cli.hbs
{
"collection": "#nestjs/schematics",
"sourceRoot": "src",
"compilerOptions": {
"assets": [
"mail/templates/**/*.hbs"
],
"watchAssets": true
}
}
The email is sent if don't send a template, so the code is working. Trying to send an email template like is my code above i get this error: TypeError: Cannot destructure property 'templateName' of 'precompile(...)' as it is undefined. Question: Why i get this issue and how to get rid of it?

Your files are located inside src/mail/templates/.
But in your module you have dir: __dirname + '/templates',.
Here __dirname returns app.module.ts folder location path which is src/.
change
dir: __dirname + '/templates',
to
dir: __dirname + '/mail/templates',

Since this bug is not yet released #743 (to this date), I rolled back to previous version:
npm i --save #nestjs-modules/mailer#1.6.0 --force

For me,this is a template file path issue.
Review your template.dir in your MailerModule config, and compare it to your project "dist" directory.
if your template file path is "dist/templates/template.hbs"
then your template.dir should be ${process.cwd()}/templates
else if your dist directory is "dist/src/templates/template.hbs" ,which is depend on your compile configs.
then your template.dir config should be join(__dirname, 'templates')

Related

nestjs-i18n Translation handlebars templates not working

Handlebars template not translating by nestjs-i18n
app.module.ts
#Global()
#Module({
imports: [
I18nModule.forRoot({
fallbackLanguage: 'en',
loaderOptions: {
path: path.join(__dirname, '/i18n/'),
watch: true,
},
resolvers: [
{ use: HeaderResolver, options: ['lang'] },
AcceptLanguageResolver,
],
}),
],
})
export class AppModule {}
mail.module.ts
doc
#Module({
imports: [
ConfigModule.forRoot(),
MailerModule.forRootAsync({
inject: [I18nService],
useFactory: (i18n: I18nService) => ({
transport: {
host: process.env.MAILER_HOST,
port: +process.env.MAILER_PORT,
ignoreTLS: true,
secure: true,
auth: {
user: process.env.MAILER_USER,
pass: process.env.MAILER_PASS,
},
},
defaults: {
from: '"No Reply" <no-reply#localhost>',
},
preview: true,
template: {
dir: path.join(__dirname, '../resources/mail/templates/'),
adapter: new HandlebarsAdapter({ t: i18n.hbsHelper }),
options: {
strict: true,
},
},
}),
}),
],
providers: [MailService],
exports: [MailService],
})
export class MailModule {}
src/i18n/fr/common.json
"HELLO": "Bonjour",
src/i18n/en/common.json
"HELLO": "Hello",
src/resources/mail/templates/test.hbs
<!doctype html>
<html>
<body>
<h1>{{ t 'common.HELLO' }}</h1>
</body>
</html>
call api endpoint with curl
curl -X POST http://localhost:8009/api/message -H "lang: fr"
in email preview i see
<!doctype html>
<html>
<body>
<h1>Hello</h1>
</body>
</html>
instead of Bonjour
Translations in another places (f.e. validation) working ok
What I'm doing wrong?
Same issue. As I can see in src/services/i18n.service.ts, property i18nLang is required in option.data.root. I think it means that we should provide property i18nLang in object, which we pass to template. In my case, I get lang value from I18nContext from controller.
F.E. I pass object in tis form
context: {
user,
i18nLang,
},
According to my experience I18nService doesnt have access to data from Resolvers. Have you tried with I18nContext or getI18nContextFromRequest? When I used them Resolvers were able to set the language.
I did another check and saw that Stas Pyatnicyn's solution works definitely.
I think he mentions this line in the hbsHelper function
const lang = options.lookupProperty(options.data.root, 'i18nLang');
But I couldnt follow up what exactly lookupProperty() does. If he can explain it, it would be very much appreciated.
Its really weird that there is no documentation about this and thanks him a lot for finding out the solution.

nodemailer + nestjs + #nestjs-modules/mailer not working with aws ses, giving `Error: Unexpected socket close` error

I am using AWS SES for SMTP credentials and this nestjs module #nestjs-modules/mailerit was working 4/5 days ago but suddenly, what happened 🤔
I am pretty sure that my credentials are right.
Error: Unexpected socket close
at Timeout._onTimeout
node_modules/nodemailer/lib/smtp-transport/index.js:189:31)
at listOnTimeout (internal/timers.js:557:17)
at processTimers (internal/timers.js:500:7)
transport: {
host: process.env.EMAIL_SERVER_HOST,
secure: false,
port: +process.env.EMAIL_SERVER_PORT,
auth: {
user: process.env.EMAIL_SERVER_USER,
pass: process.env.EMAIL_SERVER_PASSWORD,
},
},
defaults: {
from: `${process.env.EMAIL_FROM}`,
},
template: {
dir: join(__dirname, 'templates'),
adapter: new HandlebarsAdapter(),
options: {
strict: true,
},
},
}),
Edit 1: it is working on the production environment, then why it is not working on my local machine, app is hosted on cloud run :(
I got it; it is because of the wifi I am using. If I use my mobile network, it works properly.
It works on the local environment and production environment as well. I hope it helps. It requires the AWS SES key and secret, the SES SMTP user and password and the correct region.
import { Module, Global } from '#nestjs/common';
import { MailerModule } from '#nestjs-modules/mailer';
import { HandlebarsAdapter } from '#nestjs-modules/mailer/dist/adapters/handlebars.adapter';
import { MailService } from './mail.service';
import { join } from 'path';
import { ConfigService } from '#nestjs/config';
import * as AWS from 'aws-sdk';
const upperCaseFn = (name: string) => {
return name.toUpperCase();
};
#Global()
#Module({
imports: [
MailerModule.forRootAsync({
useFactory: async (config: ConfigService) => ({
transport: {
SES: new AWS.SES({
region: config.get('AWS_SES_REGION'),
accessKeyId: config.get('AWS_SES_ACCESS_KEY'),
secretAccessKey: config.get('AWS_SES_KEY_SECRET'),
}),
host: config.get('MAIL_HOST'),
port: config.get('MAIL_PORT'),
secure: false,
ignoreTLS:true,
requireTLS:false,
auth: {
user: config.get('MAIL_USERNAME'),
pass: config.get('MAIL_PASSWORD'),
},
debug: true
},
defaults: {
from: `"${config.get('MAIL_FROM_NAME')}" <${config.get(
'MAIL_FROM_ADDRESS',
)}>`,
},
template: {
dir: join(__dirname, '/templates'),
adapter: new HandlebarsAdapter({ upperCase: upperCaseFn }), // or new PugAdapter() or new EjsAdapter()
options: {
strict: true,
},
},
options: {
partials: {
dir: join(__dirname, '/templates/partials'),
options: {
strict: true,
},
},
},
}),
inject: [ConfigService],
}),
],
providers: [MailService],
exports: [MailService],
})
export class MailModule {}

configuring base url nuxt for digital ocean deployement

i'm building ecommerce web app using nuxt and node.js/express. when i'm building locally i have no problem making axios api calls. base url is configured as the following
const baseDomain = 'http://localhost:8080/';
then all i do is
async getProducts({ commit }, payload) {
const reponse = await Repository.get(
`${baseUrl}/products?${serializeQuery(payload)}`
)
.then(response => {
commit('setProducts', response.data);
return response.data;
})
.catch(error => ({ error: JSON.stringify(error) }));
return reponse;
},
now the problem is when i move my whole app to digital ocean, i tried the following changes
const baseDomain = 'https://0.0.0.0:8080/';
my nuxt.js config
export default {
ssr: false,
head: {
titleTemplate: 'Lokazz',
title: 'Lokazz',
meta: [
{ charset: 'utf-8' },
{
name: 'viewport',
content: 'width=device-width, initial-scale=1'
},
{
hid: 'description',
name: 'description',
content:
'Lokazz'
}
],
link: [
{
rel: 'stylesheet',
href:
'https://fonts.googleapis.com/css?family=Work+Sans:300,400,500,600,700&amp;subset=latin-ext'
}
]
},
css: [
'swiper/dist/css/swiper.css',
'~/static/fonts/Linearicons/Font/demo-files/demo.css',
'~/static/fonts/font-awesome/css/font-awesome.css',
'~/static/css/bootstrap.min.css',
'~/assets/scss/style.scss'
],
plugins: [
{ src: '~plugins/vueliate.js', ssr: false },
{ src: '~/plugins/swiper-plugin.js', ssr: false },
{ src: '~/plugins/vue-notification.js', ssr: false },
{ src: '~/plugins/axios.js'},
{ src: '~/plugins/lazyLoad.js', ssr: false },
{ src: '~/plugins/mask.js', ssr: false },
{ src: '~/plugins/toastr.js', ssr: false },
],
buildModules: [
'#nuxtjs/vuetify',
'#nuxtjs/style-resources',
'cookie-universal-nuxt'
],
styleResources: {
scss: './assets/scss/env.scss'
},
modules: ['#nuxtjs/axios', 'nuxt-i18n','vue-sweetalert2/nuxt', '#nuxtjs/auth-next', "bootstrap-vue/nuxt"],
bootstrapVue: {
bootstrapCSS: false, // here you can disable automatic bootstrapCSS in case you are loading it yourself using sass
bootstrapVueCSS: false, // CSS that is specific to bootstrapVue components can also be disabled. That way you won't load css for modules that you don't use
},
i18n: {
locales: [
{ code: 'en', file: 'en.json' },
],
strategy: 'no_prefix',
fallbackLocale: 'en',
lazy: true,
defaultLocale: 'en',
langDir: 'lang/locales/'
},
router: {
linkActiveClass: '',
linkExactActiveClass: 'active',
},
server: {
port: 8080, // default: 3000
host: '0.0.0.0' // default: localhost
/// this one works fine , the digital ocean support team told me to do this.
},
auth: {
strategies: {
local: {
token: {
property: "token",
global: true,
},
redirect: {
"login": "/account/login",
"logout": "/",
"home": "/page/ajouter-produit",
"callback": false
},
endpoints: {
login: { url: "/login", method: "post" },
logout: false, // we don't have an endpoint for our logout in our API and we just remove the token from localstorage
user:false
}
}
}
},
};
package.json file
{
"name": "martfury_vue",
"version": "1.3.0",
"description": "Martfury - Multi-purpose Ecomerce template with vuejs",
"author": "nouthemes",
"private": true,
"scripts": {
"dev": "nuxt",
"build": "nuxt build",
"start": "nuxt start",
"generate": "nuxt generate"
},
"config": {
"nuxt": {
"host": "0.0.0.0",
"port": "8080"
}
},
}
server index.js config
const express = require('express');
const bodyParser = require('body-parser');
const mongoose = require('mongoose')
const cors = require('cors');
const url = 'mongodb+srv://****************************' // this works fine i manage to pull data from the cluster without a problem
const jwt = require('jsonwebtoken')
mongoose.connect(url, {useNewUrlParser:true}).then(()=>{
const app = express();
// middlleware
app.use(express.json())
app.use(cors());
//products routes
const products = require('./product/product.router');
app.use('/', products)
//users routes
const users = require('./user/user.router');
app.use('/', users)
const port = process.env.PORT || 8080;
app.listen(port, () => console.log(`Server started on port ${port}`));
}).catch(error => console.log(error.reason));
const con = mongoose.connection
con.on('open', () => {
console.log('connected...')
})
here's my github repo and file structure. the server and api folder is lokazz_api.
I would recommend you use Environment variables for this.
Install dotenv in your project and then configure it in your nuxt.config.js file.
Create a .env file in your root directory, and then set a key-value pair like this:
VUE_APP_BASE_URL="<value>"
Note you need to prefix your keys with VUE_APP.
Your .env should look like this:
VUE_APP_BASE_URL="http://localhost:8080/"
You can modify your variable to this: const baseDomain = process.env.BASE_URL;
Remember to add the .env file in the .gitignore file.
On your digital ocean terminal, you can create a .env file using the touch .env command, and then use Vim or Nano to modify the file.
If your project runs fine with an .env file, it should work as good on production.
DO NOT commit .env but rather aim to your Digitalocean dashboard and look in the settings. You should see a place where you can input your pair and then proceed.
As shown here: https://docs.digitalocean.com/products/app-platform/how-to/use-environment-variables/#using-bindable-variables-within-environment-variables

NestJs not reading environmental variables

I followed the the Nest documentation to create the config but it's not working
app.module.ts
#Module({
imports: [
ConfigModule.forRoot({ isGlobal: true }),
TypeOrmModule.forRoot(config),
AuthModule,
UsersModule,
MailModule,
CloudinaryModule,
],
controllers: [AppController],
providers: [AppService],
})
.env file is on the src folder
mail.module.ts
#Module({
imports: [
MailerModule.forRoot({
transport: {
service: 'Gmail',
auth: {
user: process.env.MAIL_USER,
pass: process.env.MAIL_PASS,
},
},
}),
],
But when I run the app its undefined my key value pairs are also there.
The problem is ConfigModule's env variables are only available at run time but not on the nestjs initial state.
To allow getting the .env after nestjs initialised, you can use async config to in MailerModule.
mail.config.ts
export class MailerConfig implements MailerOptionsFactory {
createMailerOptions(): MailerOptions | Promise<MailerOptions> {
console.log(process.env.MAIL_USER); // should have value
return {
transport: {
service: 'Gmail',
auth: {
user: process.env.MAIL_USER,
pass: process.env.MAIL_PASS,
},
},
};
}
}
mail.module.ts
console.log(process.env.MAIL_USER); // undefined
#Module({
imports: [
MailerModule.forRootAsync({
useClass: MailerConfig,
}),
],
})
export class MailModule {}
you can use useFactory as well without the need of class, here I want to console.log the .env for you to check with so i used config class.

NestJs Mailer Module error upon sending email

I'm using NestJs Mailer Module, the latest stable version. You can find the documentation here.
I've search a solution for this error but I found nothing:
Error: self signed certificate in certificate chain
app.module.ts:
#Module({
imports: [
MailerModule.forRoot({
transport: 'smtps://user#domain.com:pass#smtp.domain.com',
defaults: {
from:'"nest-modules" <modules#nestjs.com>',
},
template: {
dir: __dirname + '/templates',
adapter: new HandlebarsAdapter(),
options: {
strict: true,
},
},
}),
],
})
export class AppModule {}
sending the email:
this.mailerService.sendMail({
to: 'example#domain.com',
subject: 'subject'
text: 'blahblahblah'
html: 'blahblahblah'
}).then(() => {
this.logger.log('Error email sent!', 'HttpExceptionFilter');
}).catch(err => {
this.logger.error('Error while sending error email.', err, 'HttpExceptionFilter');
});
As a solution, you can use tls: { rejectUnauthorized: false } in your transport options.

Resources