Monorepo - Quasar V2 + NestJS
Tobias Mesquita
Posted on April 13, 2021
1 - Introdução
Este artigo tem como objetivo introduzir um novo recurso do Quasar V2
, Middlewares para SSR
, este recurso nos permite extender/configurar a instancia do ExpressJS
de forma modular, assim como já fazíamos com os boots
.
Como caso de uso, iremos criar um Yarn Monorepo
, onde o frontend
vai aproveitar todo o poder do Quasar CLI
, e o backend
irá aproveitar tudo o que o seu respectivo cliente tenha a oferecer e a ponte entre ambos será um SSR Middleware
.
Desta forma, o frontend
e o backend
irão ser executados no mesmo Nó (Node)
, porém é importante que o backend
não tenha nenhuma dependência adicional para com o frontend
, se mantendo completamente desacoplado, desta forma, a qual quer momento poderemos alternar entre ser executado no seu próprio Nó (Node)
ou como um simbionte do frontend
.
Para este laboratório, estaremos usando o NestJS
, mas pode ser usado qual quer framework que possa ser montado sobre o ExpressJS
, como por exemplo o FeathersJS
.
2 - Yarn Monorepo
Para esta etapa, precisamos certificar que o NodeJS
esteja instalado, de preferencia a versão LTS
, caso esteja usando a versão Current, pode ser que enfrente problemas inesperados, seja agora ou no futuro.
Caso não o tenha, recomendo que instale através do NVM
, segue os links para o NVM Linux/Mac e para o NVM Windows.
Claro, não deixe de instalar todos os command cli
que iremos está utilizando:
npm i -g yarn@latest
npm i -g @quasar/cli@latest
npm i -g @nestjs/cli@latest
npm i -g concurrently@latest
E agora crie os seguintes arquivos na raiz do projeto:
./package.json
{
"private": true,
"workspaces": {
"packages": ["backend", "frontend"]
},
"scripts": {}
}
./.gitignore
.DS_Store
.thumbs.db
node_modules
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Editor directories and files
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
./.gitmodules
[submodule "backend"]
path = backend
url = git@github.com:${YOUR_USER}/${YOUR_BACKEND_REPO}.git
[submodule "frontend"]
path = frontend
url = git@github.com:${YOUR_USER}/${YOUR_FRONTEND_REPO}.git
Não deixe de modificar o YOUR_USER
, YOUR_BACKEND_REPO
e o YOUR_FRONTEND_REPO
para apontarem para o seu próprio repositório, isto claro, se pretende visionar este projeto.
3 - Backend Project - NestJS
Agora iremos criar o projeto do backend, para tal execute:
nest new backend
Segue as opções selecionadas:
? Which package manager would you ❤️ to use? yarn
Note que temos dois node_modules
, um na raiz do monorepo
e outro no projeto backend
, no node_modules
do monorepo
é onde é instalado a maioria das nossas dependências.
por fim, adicione alguns scripts ao ./package.json
na raiz do monorepo
:
{
"private": true,
"workspaces": {
"packages": ["backend", "frontend"]
},
"scripts": {
"backend:dev": "yarn workspace backend build:dev",
"backend:build": "yarn workspace backend build",
"backend:start": "yarn workspace backend start",
"postinstall": "yarn backend:build"
}
}
Então execute:
yarn backend:start
E acesse http://localhost:3000
4 - Backend Project - OpenAPI
A razão pela qual escolhi o NestJS para este laboratorio, é pela possibilidade de auto documentar a API com pouco ou nenhum esforço adicional. Porém pode usar qualquer outro Framework, o procedimento e os desafios devem ser bem semelhantes.
Caso prefira GraphQL à REST, você pode ignorar esta etapa, e então instalar os pacotes do NestJS para GraphQL.
Mas para tal, precisamos adicionar alguns pacotes:
yarn workspace backend add @nestjs/swagger swagger-ui-express
yarn workspace backend add --dev @types/terser-webpack-plugin
Então modifique o arquivo main.ts
em src/backend
./backend/src/main.ts
import { NestFactory } from '@nestjs/core';
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.setGlobalPrefix('api');
const config = new DocumentBuilder()
.setTitle('Quasar Nest example')
.setDescription('The cats API description')
.setVersion('1.0')
.addTag('cats')
.build();
const document = SwaggerModule.createDocument(app, config);
SwaggerModule.setup('api/docs', app, document);
await app.listen(3000);
}
bootstrap();
Por fim, execute o comando yarn backend:start
e acesse http://localhost:3000/api/docs
:
5 - Preparar o Backend para integra-lo ao Frontend
Para esta etapa, iremos precisar criar um script no backend
com uma assinatura semelhante ao do SSR Middleware
que iremos criar no frontend
e iremos mover boa parte da logica presente no main.ts
para este novo script
.
./backend/src/index.ts
import { Express, Request, Response } from 'express';
import { NestFactory } from '@nestjs/core';
import { ExpressAdapter } from '@nestjs/platform-express';
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
import { AppModule } from './app.module';
interface RenderParams {
req: Request;
res: Response;
}
interface ConfigureParams {
app: Express;
prefix: string;
render?: (params: RenderParams) => Promise<void>;
}
export default async function bootstrap({
app: server,
prefix,
render,
}: ConfigureParams) {
const app = await NestFactory.create(AppModule, new ExpressAdapter(server));
app.setGlobalPrefix(prefix);
app.useGlobalFilters({
async catch(exception, host) {
const ctx = host.switchToHttp();
const status = exception.getStatus() as number;
const next = ctx.getNext();
if (status === 404 && render) {
const req = ctx.getRequest<Request>();
const res = ctx.getResponse<Response>();
await render({ req, res });
} else {
next();
}
},
});
const config = new DocumentBuilder()
.setTitle('Quasar Nest example')
.setDescription('The cats API description')
.setVersion('1.0')
.addTag('cats')
.build();
const document = SwaggerModule.createDocument(app, config);
SwaggerModule.setup(`${prefix}/docs`, app, document);
return app;
}
E claro, modifique o main.ts
:
./backend/src/index.ts
import configure from './index';
import * as express from 'express';
async function bootstrap() {
const app = express();
const nest = await configure({ app, prefix: 'api' });
await nest.listen(3000);
}
bootstrap();
Feito isto, acesse novamente http://localhost:3030/api/docs
e veja se está tudo em ordem.
Então, precisamos alterar o package.json
em backend
, adicionando um script
em scripts
.
{
"main": "dist/index.js",
"scripts": {
"build:dev": "nest build --watch"
}
}
Caso esteja a utilizar o Quasar V1, então temos uma incopatibilidade de versões entre o Webpack usado pelo Quasar e o do NestJS, neste caso precisamos configurar o nohoist
no package.json
> workspaces
:
{
"main": "dist/index.js",
"scripts": {
"build:dev": "nest build --watch"
},
"workspaces": {
"nohoist": [
"*webpack*",
"*webpack*/**"
]
}
}
Nós precisamos deste script
, pois a configuração do Typescript no frontend
é diferente do backend
, então o Quasar CLI
não será capaz de transpilar do backend
, desta forma o frontend
irá fazer uso de um arquivo já transpilado
(dist/index.js
)
precisamos adicionar esta configuração de nohoist
ao backend
, pois as versões do webpack
e dos plugins utilizados pelo Quasar CLI
podem ser diferentes das utilizadas pelo NestJS CLI
.
por fim, caso revisite o arquivo ./package.json
, verá que tem um script de postinstall
, ele é necessário para garantir que será feito um build do backend
antes de tentar executar o frontend.
6 - Projeto do Frontend - Quasar
Assim como fizemos com o backend, precisamos criar um projeto, para tal, iremos utilizar o quasar cli:
# note que durante a elaboração deste artigo, o Quasar V2 ainda estava em beta, por isto se faz necessário o `-b next`
quasar create frontend -b next
Segue as opções selecionadas:
? Project name (internal usage for dev) frontend
? Project product name (must start with letter if building mobile apps) Quasar App
? Project description A Quasar Framework app
? Author Tobias Mesquita <tobias.mesquita@gmail.com>
? Pick your CSS preprocessor: Sass
? Check the features needed for your project: ESLint (recommended), TypeScript
? Pick a component style: Composition
? Pick an ESLint preset: Prettier
? Continue to install project dependencies after the project has been created? (recommended) yarn
As únicas recomendações que faço aqui, é que use o Yarn
e o Prettier
Então, adicione o modo ssr
, e o backend como depedencia do frontend:
cd frontend
quasar mode add ssr
cd ..
yarn workspace frontend add --dev @types/compression
yarn workspace frontend add backend@0.0.1
Caso os middlewares sejam criados como .js
, você pode transforma-los em arquivos .ts
(no momento em que este artigo foi escrito, não havia os templates para Typescript).:
./frontend/src-ssr/middlewares/compression.ts
import compression from 'compression'
import { ssrMiddleware } from 'quasar/wrappers'
export default ssrMiddleware(({ app }) => {
app.use(
compression({ threshold: 0 })
)
})
Por fim, altere o render.js
para render.ts
e faça que ele se conecte ao backend
.
./frontend/src-ssr/middlewares/render.ts
import configure from 'backend'
import { ssrMiddleware } from 'quasar/wrappers'
import { RenderError } from '@quasar/app'
export default ssrMiddleware(async ({ app, render, serve }) => {
const nest = await configure({
app,
prefix: 'api',
async render ({ req, res }) {
res.setHeader('Content-Type', 'text/html')
try {
const html = await render({ req, res })
res.send(html)
} catch (error) {
const err = error as RenderError
if (err.url) {
if (err.code) {
res.redirect(err.code, err.url)
} else {
res.redirect(err.url)
}
} else if (err.code === 404) {
res.status(404).send('404 | Page Not Found')
} else if (process.env.DEV) {
serve.error({ err, req, res })
} else {
res.status(500).send('500 | Internal Server Error')
}
}
}
});
await nest.init()
});
Por fim, modifique o package.json > scripts
do frontend
e adicione os seguintes scripts:
{
"scripts": {
"dev": "quasar dev -m ssr",
"build": "quasar build -m ssr"
}
}
E para que possamos testar, modifique o package.json > scripts
do monorepo:
./package.json
{
"private": true,
"workspaces": {
"packages": ["backend", "frontend"]
},
"scripts": {
"backend:dev": "yarn workspace backend build:dev",
"backend:build": "yarn workspace backend build",
"backend:start": "yarn workspace backend start",
"frontend:dev": "yarn workspace frontend dev",
"start": "yarn backend:start",
"dev": "concurrently \"yarn backend:dev\" \"yarn frontend:dev\"",
"postinstall": "yarn backend:build"
}
}
Então execute:
yarn dev
Então acesse http://localhost:8080
para verificar que o frontend
está funcionando, então http://localhost:8080/api/docs
para verificar que o backend
está em ordem.
Posted on April 13, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.