DEV Community

Tobias Mesquita for Quasar Framework Brasil

Posted on • Edited on

5

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

Image of Datadog

How to Diagram Your Cloud Architecture

Cloud architecture diagrams provide critical visibility into the resources in your environment and how they’re connected. In our latest eBook, AWS Solution Architects Jason Mimick and James Wenzel walk through best practices on how to build effective and professional diagrams.

Download the Free eBook

Top comments (0)

Image of Docusign

🛠️ Bring your solution into Docusign. Reach over 1.6M customers.

Docusign is now extensible. Overcome challenges with disconnected products and inaccessible data by bringing your solutions into Docusign and publishing to 1.6M customers in the App Center.

Learn more