DEV Community

Tobias Mesquita for Quasar Framework Brasil

Posted on • Edited on

Monorepo - Quasar V2 + NestJS

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 
Enter fullscreen mode Exit fullscreen mode

Alt Text

E agora crie os seguintes arquivos na raiz do projeto:

./package.json

{ "private": true, "workspaces": { "packages": ["backend", "frontend"] }, "scripts": {} } 
Enter fullscreen mode Exit fullscreen mode

./.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 
Enter fullscreen mode Exit fullscreen mode

./.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 
Enter fullscreen mode Exit fullscreen mode

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.

Alt Text

3 - Backend Project - NestJS

Agora iremos criar o projeto do backend, para tal execute:

nest new backend 
Enter fullscreen mode Exit fullscreen mode

Segue as opções selecionadas:

? Which package manager would you ❤️ to use? yarn 
Enter fullscreen mode Exit fullscreen mode

Alt Text

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" } } 
Enter fullscreen mode Exit fullscreen mode

Então execute:

yarn backend:start 
Enter fullscreen mode Exit fullscreen mode

Alt Text

E acesse http://localhost:3000
Alt Text

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 
Enter fullscreen mode Exit fullscreen mode

Alt Text

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(); 
Enter fullscreen mode Exit fullscreen mode

Por fim, execute o comando yarn backend:start e acesse http://localhost:3000/api/docs:
Alt TextAlt Text

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; } 
Enter fullscreen mode Exit fullscreen mode

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(); 
Enter fullscreen mode Exit fullscreen mode

Alt Text

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" } } 
Enter fullscreen mode Exit fullscreen mode

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*/**" ] } } 
Enter fullscreen mode Exit fullscreen mode

Alt Text

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 
Enter fullscreen mode Exit fullscreen mode

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 
Enter fullscreen mode Exit fullscreen mode

As únicas recomendações que faço aqui, é que use o Yarn e o Prettier

Alt Text

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 
Enter fullscreen mode Exit fullscreen mode

Alt Text

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 }) ) }) 
Enter fullscreen mode Exit fullscreen mode

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() }); 
Enter fullscreen mode Exit fullscreen mode

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" } } 
Enter fullscreen mode Exit fullscreen mode

Alt Text

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" } } 
Enter fullscreen mode Exit fullscreen mode

Alt Text

Então execute:

yarn dev 
Enter fullscreen mode Exit fullscreen mode

Então acesse http://localhost:8080 para verificar que o frontendestá funcionando, então http://localhost:8080/api/docs para verificar que o backend está em ordem.

Alt TextAlt Text

Top comments (0)