Skip to content

Instantly share code, notes, and snippets.

What would you like to do?
NuxtJS / NestJS fullstack development

This is my take on fullstack development using NuxtJS and NestJS.

Development steps

  • Lerna monorepo
  • TypeScript linting and formatting
  • Minimal NestJS server
  • Minimal NuxtJS client
  • Environment configuration
  • TypeGraphQL
  • TypeORM / MySQL
  • HTTP proxy middleware
  • Apollo Client
  • User datamodel, queries and mutations
  • JWT / local authentication
  • Server-side authorization guards
  • Buefy / SCSS
  • Form handling and validation
  • Login / Registration / Profile forms

We assume basic knowledge of NodeJS.

Lerna monorepo

We first install Lerna globally, initialize a new git repository and turn it into a monorepo with independent versioning:

npm install --global lerna
git init nuxtnest && cd nuxtnest
lerna init --independent

Let's adjust lerna.json so we can benefit from Yarn workspaces:

  "packages": [
  "version": "independent",
  "npmClient": "yarn",
  "useWorkspaces": true

We’ll also adjust package.json to rename our project and define where the Yarn workspaces are located:

  "name": "nuxtnest",
  "private": true,
  "workspaces": [
  "scripts": {
    "dev": "lerna run dev --stream --no-bail",
    "build": "lerna run build --stream --no-bail",
    "start": "lerna run start --stream --no-bail"
  "devDependencies": {
    "lerna": "^3.18.3"

As you can see, we also added some scripts:

  • dev compiles and executes the project in development mode (watch for changes and recompiles on-the-fly)
  • build compiles the packages for production
  • start executes the compiled project (build must be run before)

We should probably create .gitignore with sensible defaults:


We are now ready to configure our project for TypeScript development.

TypeScript linting and formatting

We first install TypeScript globally:

yarn add -WD typescript

A common practice with TypeScript monorepos is to have a shared tsconfig.json at the root of the project. In this file, we want to configure our TypeScript defaults like declaration, source map, etc.

  "compilerOptions": {
    "declaration": true,
    "sourceMap": true,
    "skipLibCheck": true

This file should be included in every package's tsconfig.json with the following:

"extends": "../../tsconfig.json"

Now let's install and configure ESLint and Prettier:

yarn add -WD eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin
yarn add -WD prettier eslint-config-prettier eslint-plugin-prettier

Then we configure Prettier with the .prettierrc file:

  "tabWidth": 2,
  "trailingComma": "es5",
  "semi": true,
  "singleQuote": true

...and ESLint with the .eslintrc file:

  "root": true,
  "parser": "@typescript-eslint/parser",
  "extends": [
  "parserOptions": {
    "ecmaVersion": 2018,
    "sourceType": "module"
  "rules": {
    "@typescript-eslint/explicit-function-return-type": "off"

Finally, we add the global lint script to package.json:

"lint": "eslint --ext .js,.ts --ignore-path .gitignore --fix packages/"

As a bonus, we can configure VSCode to automatically fix the code whenever a file is saved. This is achieved by installing the ESLint extension and setting ESLint: Run to onSave.

Minimal NestJS server

Let's create our server package:

mkdir -p packages/server && cd packages/server
yarn init -py

We use a manual NestJS configuration (without using the Nest CLI) for greater control, using ts-node-dev as our build tool:

yarn add @nestjs/common @nestjs/core @nestjs/platform-express reflect-metadata rxjs
yarn add -D ts-node-dev

Again, let's tweak the server's package.json for our needs:

  • Rename the package with the @nuxtnest/ prefix
  • Set main to the compiled dist/main.js javascript file
  • Add our dev / build / start scripts
  "name": "@nuxtnest/server",
  "version": "1.0.0",
  "private": true,
  "main": "dist/main.js",
  "scripts": {
    "dev": "ts-node-dev --no-notify --transpileOnly src/main.ts",
    "build": "tsc",
    "start": "node dist/main.js"
  "dependencies": {
    "@nestjs/common": "^6.10.11",
    "@nestjs/core": "^6.10.11",
    "@nestjs/platform-express": "^6.10.11",
    "reflect-metadata": "^0.1.13",
    "rxjs": "^6.5.3"
  "devDependencies": {
    "ts-node-dev": "^1.0.0-pre.44"

Before we can start writing TypeScript code inside src/, we have to create the server's tsconfig.json with the following in mind:

  • Don't forget to extend our shared tsconfig.json
  • NestJS requires decorator metadata
  • Compiled TS code should be in dist/
  • TS source code should reside in src/
  "extends": "../../tsconfig.json",
  "compilerOptions": {
    "target": "es2017",
    "module": "commonjs",
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,
    "outDir": "./dist"
  "include": [

Our minimal server consists of a single, empty application module that is used to bootstrap NestJS from the main file.

Let's create the two corresponding files:

// src/app.module.ts
import { Module } from '@nestjs/common';

export class AppModule { }
// src/main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  await app.listen(4000);

We should now be able to start our server using the previously defined scripts:

# Compile, execute and watch for changes (development)
yarn dev

# Compile and execute (production)
yarn build && yarn start

Navigating to http://localhost:4000 shoud bring up the standard 404 response from NestJS.

Minimal NuxtJS client

Let's create our client package:

mkdir packages/client && cd packages/client
yarn init -py

We install NuxtJS with TypeScript runtime / build support:

yarn add nuxt @nuxt/typescript-runtime
yarn add -D @nuxt/typescript-build

We then configure TypeScript tsconfig.json:

  "extends": "../../tsconfig.json",
  "compilerOptions": {
    "target": "es2018",
    "module": "esnext",
    "moduleResolution": "node",
    "lib": [
    "esModuleInterop": true,
    "allowJs": true,
    "strict": true,
    "noEmit": true,
    "baseUrl": ".",
    "paths": {
      "~/*": [
      "@/*": [
    "types": [
  "exclude": [

... and NuxtJS nuxt.config.ts:

import { Configuration } from '@nuxt/types';

const nuxtConfig: Configuration = {
  buildModules: ['@nuxt/typescript-build'],

export default nuxtConfig;

Finally we adjust package.json with the name prefix and scripts:

  "name": "@nuxtnest/client",
  "version": "1.0.0",
  "private": true,
  "scripts": {
    "dev": "nuxt-ts",
    "build": "nuxt-ts build",
    "start": "nuxt-ts start"
  "dependencies": {
    "@nuxt/typescript-runtime": "^0.3.3",
    "nuxt": "^2.11.0"
  "devDependencies": {
    "@nuxt/typescript-build": "^0.5.2"

We won't create anything inside pages/ for now as we only want to have a working minimal NuxtJS client.

Environment configuration

One of the advantages of using a monorepo is sharing code between packages, which can prove useful for defining common configuration values or secrets.

Let's create a shared package for this purpose:

mkdir packages/shared && cd packages/shared
yarn init -py

Like before, we create tsconfig.json to configure TypeScript:

  "extends": "../../tsconfig.json",
  "compilerOptions": {
    "outDir": "./dist"
  "include": [

... and adjust package.json:

  "name": "@nuxtnest/shared",
  "version": "1.0.0",
  "main": "dist/index.js",
  "private": true,
  "scripts": {
    "dev": "tsc -w",
    "build": "tsc"

We use TypeScript watch mode (tsc -w) for dev and we don't define a start script since this package is not to be executed.

Also, since we might need to share code for other purposes, we'll use src/index.ts as an entry point for the package and export envconfig.ts from there:

// src/index.ts
export * from './envconfig';

We want our environment configuration to have the following features:

  • Type-safe via interface EnvConfig
  • Values stored inside the .env file at the root of the project
  • Values can be overridden by system environment variables
  • Sensible default values whenever possible (required otherwise)
  • Throw an error when a required value is missing

We'll use dotenv and env-var to help us achieve this:

yarn add dotenv env-var
yarn add -D @types/dotenv

As a starting point, let's define client / server port numbers. For such a configuration, our envconfig.ts file would look something like this:

// src/envconfig.ts
import * as env from 'env-var';
import { config } from 'dotenv';
import { resolve } from 'path';

config({ path: resolve(__dirname, '../../../.env') });

export interface EnvConfig {
  clientPort: number;
  serverPort: number;

export const envConfig: EnvConfig = {
  clientPort: env.get('CLIENT_PORT', '3000').asPortNumber(),
  serverPort: env.get('SERVER_PORT', '4000').asPortNumber(),

To use our new package from elsewhere in the project, we first need to link the dependencies using Lerna (from the root of the project):

lerna bootstrap

Then we can import from anywhere using:

import { envConfig } from '@nuxtnest/shared';


Inside packages/server, we install the necessary packages

yarn add @nestjs/graphql apollo-server-express graphql-tools graphql type-graphql

This comment has been minimized.

Copy link

wallopthecat commented Jun 30, 2020

Very nice guide, but dotenv doesn't work in nuxt due to fs dependency. I have yet to try any workarounds yet


This comment has been minimized.

Copy link
Owner Author

lewebsimple commented Jun 30, 2020

@wellopthecat You can import the env config inside nuxt.config.ts and pass the values to the client (and server) at build time.


This comment has been minimized.

Copy link

wallopthecat commented Jun 30, 2020

Thanks, very smart. The new way to do this in Nuxt is using these 2 props in nuxt.config

  publicRuntimeConfig: {},
  privateRuntimeConfig: {},


Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.