DEV Community

Cover image for NestJS - Adding a frontend to the monorepo
Michael "lampe" Lazarski
Michael "lampe" Lazarski

Posted on

NestJS - Adding a frontend to the monorepo

In the last two blog posts, we created a Monorepo and integrated Redis. You can find them here:

In this blog post, we will add Vue as our frontend and make it work within our Monorepo.

Installing the dependencies

Let's first get install our dependencies:

yarn add vue

And now our developer dependencies

yarn add -D babel-loader css-loader file-loader html-webpack-plugin node-sass sass-loader url-loader vue-loader vue-template-compiler webpack webpack-bundle-analyzer webpack-cli webpack-dev-server vue-eslint-parser

As you can see, we need to install way more dependencies for development. Most of them are dependencies to make Webpack build and serve our frontend.
Webpack will handle HTML, vue, css, sass and files.

Creating the frontend

First, we need to create a folder named 'frontend'

mkdir frontend

In that folder, we will have all of our 'frontends'. For this example, we want to create our frontend for our 'blog' backend.

cd frontend
mkdir blog

Now we need to create an index.html file. This will be the entry file to the blog frontend.

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width,initial-scale=1.0" />
    <title>My Vue app with webpack 4</title>
  </head>

  <body>
    <div id="app"></div>
  </body>
</html>

The most important line here is the div with the id="app". VueJS needs this div as an entry point.

The next file we need is a webpack.config.js

/* eslint-disable @typescript-eslint/no-var-requires */
const path = require('path');
const VueLoaderPlugin = require('vue-loader/lib/plugin');
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
const HtmlPlugin = require('html-webpack-plugin');

const config = {
  context: __dirname,
  entry: './src/index.ts',
  output: {
    path: path.resolve(process.cwd(), 'dist/frontend'),
    filename: '[name].[contenthash].js'
  },
  target: 'web',
  module: {
    rules: [
      {
        test: /\.vue$/,
        loader: 'vue-loader'
      },
      {
        test: /\.css$/,
        use: [
          'vue-style-loader',
          'css-loader'
        ]
      },
      {
        test: /\.ts$/,
        loader: "ts-loader",
        options: { appendTsSuffixTo: [/\.vue$/] },
        exclude: /node_modules/
      },
      {
        test: /\.scss$/,
        use: [
          'vue-style-loader',
          'css-loader',
          'sass-loader'
        ]
      },
      {
        test: /\.svg$/,
        use: 'file-loader'
      },
      {
        test: /\.png$/,
        use: [
          {
            loader: 'url-loader',
            options: {
              mimetype: 'image/png'
            }
          }
        ]
      }
    ]
  },
  resolve: {
    extensions: [
      '.js',
      '.vue',
      '.tsx',
      '.ts'
    ]
  },
  plugins: [
    new HtmlPlugin({
      template: 'index.html',
      chunksSortMode: 'dependency'
    }),
    new BundleAnalyzerPlugin({
      analyzerMode: 'static',
      openAnalyzer: false,
    }),
    new VueLoaderPlugin(),
  ],
  optimization: {
    runtimeChunk: 'single',
    splitChunks: {
      cacheGroups: {
        vendor: {
          test: /[\\/]node_modules[\\/]/,
          name: 'vendors',
          chunks: 'all'
        }
      }
    }
  },
  devServer: {
    contentBase: path.join(__dirname, 'public'),
    compress: true,
    port: 9000
  }
};

module.exports = config;

Webpack configs are fun! Let's start from the bottom. The devServer will run on port 9000 and will look for files in the public. For that to work, we need to set the context option to __dirname. __dirname will resolve to the path that the directory is currently in, in our case, the blog frontend folder. entry is the file that bootstraps and we will create it next. In the output we need to specify the path. process.cwd() will resolve to the main project folder, and we are adding dist/frontend. This means you can find there our frontend files. The rest is configuration to get Vue running with typescript, to load CSS, SCSS, SVG and png files.

Typescript also needs a config.

{
  "compilerOptions": {
    "outDir": "./dist/",
    "sourceMap": true,
    "strict": true,
    "noImplicitReturns": true,
    "noImplicitAny": true,
    "module": "es6",
    "moduleResolution": "node",
    "target": "es5",
    "allowJs": true
  },
  "include": [
    "./blog/src/**/*"
  ]
}

This is a pretty standard ts config. We need to include our blog/src folder. Without this, you will get a typescript error.

Now let us create our src/index.ts file, src/App.vue file and src/vue-shim.d.ts.

index.ts:

import Vue from 'vue';
import App from './App.vue';

new Vue({
  el: '#app',
  render: h => h(App),
});

This is the default VueJS setup.

App.vue

<template>
  <h1>lampeweb dev blog</h1>
</template>

<script lang="ts">
import Vue from 'vue';

export default Vue.extend({
  data: function() {
    return {
      name: 'Hello World!',
    };
  },
});
</script>

Thanks to our Webpack config we can already use typescript in our Vue components. This file is a simple Vue component which just will display a header with the text lampeweb dev blog.

vue-shim.d.ts:

declare module '*.vue' {
  import Vue from 'vue';
  export default Vue;
}

This will make typescript and your editor happy :). Do you want to know more about how declare module works? Leave a comment!

We need now to define our npm scripts next.

{
  "scripts": {
    "f:blog:dev:watch": "webpack-dev-server -d --mode development --config ./frontend/blog/webpack.config.js",
    "f:blog:build": "webpack -p --mode production  --config ./frontend/blog/webpack.config.js"
  }
}

We can now test if everything worked with:

yarn run f:blog:dev:watch

After Webpack has built our frontend, you should see the following:
Alt Text

I hope you liked that post! If you want a follow-up, please comment, like, and share. So I can know that you are interested in content like that!

👋Say Hello! Instagram | Twitter | LinkedIn | Medium | Twitch | YouTube

Top comments (10)

Collapse
 
eliotik profile image
Alexander

HI, thank you for articles, easy to read, except this one.
It named: "NestJS - Adding a frontend to the monorepo"
But in fact you created a separate folder to nestjs apps and in your vuejs app there is no api calls to nestjs app.
Maybe I'm missing something, please, correct me if I'm wrong.
Thank you.

Collapse
 
lampewebdev profile image
Michael "lampe" Lazarski

Hey,

I'm confused a little bit :D
You can still make api calls to nestjs?
Or is it because of no examples in this post?

Nest and the frontend should talk over http anyway so in production they can live on separate servers.
So yeah I'm confused :D

Collapse
 
eliotik profile image
Alexander • Edited

😄
My idea is that this part can live separately from nest articles, because:

  • you created vue app
  • setup webpack config
  • no connection with previous two articles.

I'd either separate it or update by showing how to include view app in the nest folders stricture and added one simple API call
I hope I clarified welly comment this time 🙂

Thread Thread
 
lampewebdev profile image
Michael "lampe" Lazarski

Okay.

At the end of the day, you need to use what fits you and the project the most.

So yeah you don't need to separate them ore make a mono repo.

You do you and what fits the project the most :)

Collapse
 
douglance profile image
Doug Lance

How do you serve this frontend with Nest.js?

Collapse
 
lampewebdev profile image
Michael "lampe" Lazarski

Why do you want to serve it with nestjs?

Each frontend and backend should be its own process or server.
Serving them from the same process or server would defeat the purpose of splitting them up.

Collapse
 
patarapolw profile image
Pacharapol Withayasakpunt

I see that there is Redis.

Might you put a Nginx tutorial?

Thread Thread
 
lampewebdev profile image
Michael "lampe" Lazarski

Nginx is something to setup for production.

So if I will use Nginx this will come later :)

Collapse
 
wallopthecat profile image
wallopthecat

What about SSR?

Collapse
 
empz profile image
Emiliano Parizzi

Nice!
Do you have the full repo on Github or somewhere?