npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

create-vercel-http-server-handler

v1.0.3

Published

### Versel’s Philosophy

Downloads

12

Readme

The definitive guide to running servers in Vercel

Versel’s Philosophy

It's possible to deploy an Express.js application as a single Serverless Function, but it comes with drawbacks and should only be used as a migration path. Instead, embrace multiple Serverless Functions as you incrementally migrate to the Vercel platform.

My Philosophy

While there are cases where following Vercel’s approach is beneficial, I disagree with entirely dismissing these frameworks. With frameworks like Express.js and Nest.js, I can:

  • keep my application portable among hosting providers (No vendor locking)
  • take advantage of the enormouse ecosystem of existing packages, tools, and features built around these frameworks, without reinventing the wheel each time (DRY)
  • reference many resources to learn, build, and bug fix my application in a large community (many resources)

The main drawback† is a slightly longer cold start time when initializing the framework and risking the 50MB limit. This package caches your server so this delay will not be an issue in subsequent requests.

Use cases

This package has three main helper functions:

  • createNextHandler (expessjs and nestjs)
  • createVercelHandler (nestjs)
  • createLambdaHandler (nestjs)

Next.js

Install

This quick start assumes you bootstrapped your function with npx create-next-app project-name. However, this package should work with any Zero Config Deployments.

Install this package via npm, npm install create-vercel-http-server-handler

Be sure you have installed the dependencies of your framework in your project, as this package relies on them.

For Express, npm install express

For Nest.js, npm install @nestjs/core @nestjs/common @nestjs/platform-express

Setup

Inside your api folder, create a catch all API route. For example, make a file named [...slug].ts. Inside that file, import this package:

import {
  createNextHandler,
  bootstrapExpress,
  bootstrapNest,
} from 'create-vercel-http-server-handler';

Export default the handler helper for your framework of choice, and disable the bodyParser.

Express.js

export default createNextHandler({
  bootstrap: bootstrapExpress({ app }),
});

export const config = {
  api: {
    bodyParser: false,
  },
};

Nest.js

This package expects you to use the default @nestjs/platform-express under the hood. It will not work with @nestjs/platform-fastify. Check out the example on github.

When using typescript, don’t forget to npm install --save-dev typescript @types/react @types/node

Optionally, create a useGlobal function for Nest.js to apply any global prefixes, pipes, filters, guards, and interceptors. Because Next.js api routes are all prefixed with /api, we recommend you do the same. Only start the server when invoked by the Nest.js CLI. Here is an example src/main.ts:

import { NestFactory } from '@nestjs/core';
import { INestApplication, NestApplicationOptions } from '@nestjs/common';
import { AppModule } from './server/app/app.module';

export const nestApplicationOptions: NestApplicationOptions = {
  logger: false,
};

export async function useGlobal(app: INestApplication) {
  app.setGlobalPrefix('/api');
}

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  await useGlobal(app);
  await app.listen(Number(process.env.NEST_PORT) || 3000);
}
if (process.env.CLI === 'NEST') {
  bootstrap();
}

Pass your AppModule and optional useGlobal function to the bootstrapNest helper function inside your [...slug].ts api route.

import {
  createNextHandler,
  bootstrapNest,
} from 'create-vercel-http-server-handler';
import { AppModule } from '../../server/app/app.module';
import { useGlobal, nestApplicationOptions } from '../../main';

export default createNextHandler({
  bootstrap: bootstrapNest({
    AppModule,
    useGlobal,
    nestApplicationOptions,
  }),
  NODE_ENV: process.env.NODE_ENV,
  NEST_PORT: Number(process.env.NEST_PORT),
});

export const config = {
  api: {
    bodyParser: false,
  },
};

Nest.js also relies on experimental TypeScript features. Install the required dependencies:

npm install --save-dev @babel/plugin-transform-runtime babel-plugin-transform-typescript-metadata @babel/plugin-proposal-decorators @babel/plugin-proposal-class-properties

Enable the experimental TypeScript features with a .babelrc file:

{
  "presets": ["next/babel"],
  "plugins": [
    [
      "@babel/plugin-transform-runtime",
      {
        "regenerator": true
      }
    ],
    "babel-plugin-transform-typescript-metadata",
    ["@babel/plugin-proposal-decorators", { "legacy": true }],
    ["@babel/plugin-proposal-class-properties", { "loose": true }]
  ]
}

Edit the tsconfig.json for Next.js:

{
  "compilerOptions": {
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "baseUrl": "./",

    "target": "es5",
    "lib": ["dom", "dom.iterable", "esnext"],
    "allowJs": true,
    "skipLibCheck": true,
    "strict": false,
    "forceConsistentCasingInFileNames": true,
    "noEmit": true,
    "esModuleInterop": true,
    "module": "esnext",
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "jsx": "preserve"
  },
  "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
  "exclude": ["node_modules", "dist", ".next", ".vercel", "scripts"]
}

Add the tsconfig.nest.json for Nest.js:

{
  "compilerOptions": {
    "forceConsistentCasingInFileNames": true,
    "esModuleInterop": true,
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,

    "module": "commonjs",
    "declaration": true,
    "removeComments": true,
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "allowSyntheticDefaultImports": true,
    "target": "es2017",
    "sourceMap": true,
    "outDir": "./dist",
    "baseUrl": "./",
    "incremental": true
  },
  "exclude": ["node_modules", "dist", ".next", ".vercel", "scripts"]
}

Don’t forget to install all of Nest.js’ dependencies and dev dependencies. Plus, move relevant scripts, configs, .gitignores, and other files. Consider moving all the server files into src/server/* and moving the main.ts into src/main.ts.

npm install @nestjs/common @nestjs/core @nestjs/platform-express reflect-metadata rimraf rxjs

npm install --save-dev @nestjs/cli @nestjs/schematics @nestjs/testing @types/express @types/jest @types/node @types/supertest @typescript-eslint/eslint-plugin @typescript-eslint/parser eslint eslint-config-prettier eslint-plugin-import jest prettier supertest ts-jest ts-loader ts-node tsconfig-paths typescript

Update the nest-cli.json to reflect the new organization:

{
  "collection": "@nestjs/schematics",
  "sourceRoot": "src/server",
  "compilerOptions": {
    "plugins": ["@nestjs/graphql/plugin"]
  }
}

Finally, update the scripts inside the package.json and install the required script dependencies:

npm install --save-dev npm-run-all cross-env wait-on

{
  "scripts": {
    "predev": "rimraf dist",
    "dev": "npm-run-all -p -r dev:nest dev:next:wait",
    "dev:next": "cross-env NEST_PORT=7000 next dev -p 8000",
    "dev:next:wait": "npm-run-all -s dev:nest:wait dev:next",
    "dev:nest": "cross-env NEST_PORT=7000 CLI=NEST nest start --path ./tsconfig.nest.json --watch --preserveWatchOutput",
    "dev:nest:wait": "wait-on tcp:7000",
    "build": "npm run build:next",
    "build:next": "next build",
    "prebuild:nest": "rimraf dist",
    "build:nest": "cross-env NODE_ENV=production nest build --path ./tsconfig.nest.json",
    "start": "npm run start:next",
    "start:next": "next start -p 8000",
    "start:nest": "cross-env CLI=NEST NEST_PORT=7000 nest start --path ./tsconfig.nest.json",
    "start:nest:prod": "cross-env CLI=NEST NEST_PORT=7000 node dist/main",
    "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
    "test": "jest",
    "test:watch": "jest --watch",
    "test:cov": "jest --coverage",
    "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
    "test:e2e": "jest --config ./test/jest-e2e.json"
  }
}

If you are using graphql, customize the webpack config in next.config.js and npm install --save-dev ts-loader:

module.exports = {
  webpack: (config, { isServer }) => {
    if (isServer) {
      const tsLoader = {
        test: /\.tsx?$/,
        loader: 'ts-loader',
        options: {
          transpileOnly: true,
          getCustomTransformers: program => ({
            before: [require('@nestjs/graphql/plugin').before({}, program)],
          }),
        },
        exclude: /node_modules/,
      };
      config.module.rules.push(tsLoader);
    }
    return config;
  },
};

If you are creating custom scripts, you will need another tsconfig.scripts.json:

{
  "compilerOptions": {
    "noEmit": true,

    "forceConsistentCasingInFileNames": true,
    "esModuleInterop": true,
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,

    "module": "commonjs",
    "declaration": true,
    "removeComments": true,
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "allowSyntheticDefaultImports": true,
    "target": "es2017",
    "sourceMap": true,
    "baseUrl": "./",
    "incremental": true
  },
  "exclude": ["node_modules", "dist", ".next", ".vercel"]
}

Serverless Configuration

For additional configuration, read Vercel’s docs.

vercel.json

{
  "version": 2,
  "scope": "your-scope",
  "functions": {
    "src/pages/api/[...slug].ts": {
      "memory": 3008,
      "maxDuration": 60
    }
  }
}

How it works

The first argument of createNextHandler is your bootstrap function. The second argument is enableCache to cache your server after startup. I recommend using !!process.env.AWS_REGION so that your server is cached on Vercel hosting, but will still hot reload properly locally.

Internally, we call http.createServer(expressApp) and cache your server after calling app.listen(port, () => { … });. We proxy your server by forwarding all of Vercel’s requests via node-http-proxy. Essentially, we are running a server inside a serverless function.

AWS Lambda

Install

This QuickStart assumes you bootstrapped your function with nest new project-name. However, this package should work with any Zero Config Deployments.

Install this package via npm, npm install create-vercel-http-server-handler

Be sure you have installed the dependencies of your framework in your project, as this package relies on them.

For express, npm install aws-serverless-express @nestjs/platform-express

For claudia, npm install claudia --save-dev

TypeScript

Edit your tsconfig.json file:

{
  "compilerOptions": {
    "lib": ["dom", "dom.iterable", "esnext"],
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "esModuleInterop": true,
    "resolveJsonModule": true,
    "isolatedModules": true,

    "module": "commonjs",
    "declaration": true,
    "removeComments": true,
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "allowSyntheticDefaultImports": true,
    "target": "es2017",
    "sourceMap": true,
    "outDir": "./dist",
    "baseUrl": "./",
    "incremental": true
  }
}

Setup

Inside your source folder, extract all your global app settings into src/useGlobal.ts:

import helmet from 'helmet'; // npm i helmet
import { UseGlobal } from 'create-vercel-http-server-handler';

export const useGlobal: UseGlobal = async app => {
  app.use(helmet());
  app.enableCors();

  // only exists in NestExpressApplication
  if ('disable' in app) app.disable('x-powered-by');

  return app;
};

Refactor your src/main.ts to use the useGlobal.ts function:

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app/app.module';
import { useGlobal } from './useGlobal';

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

Create your src/lambda.ts file:

import { createLambdaHandler } from 'create-vercel-http-server-handler';
import { AppModule } from './app/app.module';
import { useGlobal } from './useGlobal';

module.exports.handler = createLambdaHandler({
  AppModule,
  useGlobal,
});

Claudia.js

We will deploy our function with Claudia.js. Be sure to follow their setup instructions.

Unfortunately, Claudia.js does not support native modules (which are c++ libraries built for specific NodeJS versions and operating systems), like sharp.js. We will adapt steps from this guide to script our own support. Be sure you have docker installed.

Scripts

Inside project-name/scripts/claudia-create.sh:

docker run -v $PWD:/claudia -v $HOME/.aws:/root/.aws --rm lambci/lambda:build-nodejs12.x /bin/bash -c "\
cd /claudia
rm -rf node_modules
npm install
npm run build
npm run claudia-create
"

Inside project-name/scripts/claudia-update.sh:

docker run -v $PWD:/claudia -v $HOME/.aws:/root/.aws --rm lambci/lambda:build-nodejs12.x /bin/bash -c "\
cd /claudia
rm -rf node_modules
npm install
npm run build
npm run claudia-update
"

Make the scripts executable by running chmod +x ./scripts/claudia-create.sh and chmod +x ./scripts/claudia-update.sh in your terminal. You should only need to do this once.

Add these scripts to your package.json:

{
  "claudia-create": "claudia create --handler dist/lambda.handler --deploy-proxy-api --region us-east-1 --timeout 29",
  "claudia-update": "claudia update --timeout 29",
  "create": "./scripts/claudia-create.sh",
  "update": "./scripts/claudia-update.sh"
}

To run Nest.js locally again, be sure to reinstall your dependencies with npx rimraf node_modules and npm install. You will need to do this after every deployment.

Finally, you will need to add these fields to your package.json for Claudia.js to behave correctly:

{
  "files": ["dist"],
  "main": "lambda.js"
}

or

{
  "main": "dist/lambda.js"
}

Avoiding the 50MB limit

No matter what, claudiajs will always include your production dependencies in your node_modules folder. However, there are often unnecessary files included with your package.

To avoid hitting the 50MB limit, use a package to prune the node_modules folder using:

  • https://www.npmjs.com/package/modclean
  • https://www.npmjs.com/package/node-prune
Modclean

Run npm install modclean --save-dev

Add a postinstall script to your package.json:

{
  "postinstall": "npx modclean -n default:caution --no-progress --run"
}

Inside your docker scripts, add npm set unsafe-perm true before running any npm commands to fix the cannot run in wd error.

Vercel Serverless

This QuickStart assumes you bootstrapped your function with nest new project-name. However, this package should work with any Zero Config Deployments.

Install this package via npm, npm install create-vercel-http-server-handler and npm install --save-dev vercel

TypeScript

Edit your tsconfig.json file:

{
  "compilerOptions": {
    "lib": ["dom", "dom.iterable", "esnext"],
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "esModuleInterop": true,
    "resolveJsonModule": true,
    "isolatedModules": true,

    "module": "commonjs",
    "declaration": true,
    "removeComments": true,
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "allowSyntheticDefaultImports": true,
    "target": "es2017",
    "sourceMap": true,
    "outDir": "./dist",
    "baseUrl": "./",
    "incremental": true
  }
}

Setup

Inside your source folder, extract all your global app settings into src/useGlobal.ts:

import helmet from 'helmet'; // npm i helmet
import { UseGlobal } from 'create-vercel-http-server-handler';

export const useGlobal: UseGlobal = async app => {
  app.use(helmet());
  app.enableCors();

  // only exists in NestExpressApplication
  if ('disable' in app) app.disable('x-powered-by');

  return app;
};

Refactor your src/main.ts to use the useGlobal.ts function:

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app/app.module';
import { useGlobal } from './useGlobal';

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

Create your src/vercel.ts file:

import { createVercelHandler } from 'create-vercel-http-server-handler';
import { AppModule } from './app/app.module';
import { useGlobal } from './useGlobal';

export default createVercelHandler({
  AppModule,
  useGlobal,
});

Vercel

Edit your vercel.json file:

{
  "version": 2,
  "cleanUrls": true,
  "rewrites": [
    { "source": "/api/vercel", "destination": "/api/vercel" },
    { "source": "/", "destination": "/api/vercel" },
    { "source": "/:match*", "destination": "/api/vercel" }
  ]
}

Note: the root path is a bit buggy. Switch the example @Get() to @Get('/hello') in src/app/app.controller.ts to demo.

Add project-name/api/vercel.js:

import Handler from '../dist/vercel';

export default Handler;

export const config = {
  api: {
    bodyParser: false,
  },
};

Run npx vercel to setup and deploy your project. Choose the defaults, except for the Output Directory option—select dist, where Nestjs compiles your code (otherwise you will have an infinite loop with npx vercel dev).

To deploy to production, run npx vercel --prod. Vercel handles all native dependencies for you automatically.

Footnotes

† Please note that serverless in general does not scale well when directly connecting to a database like MongoDB or PostgreSQL. Be sure you use connection pools.

Thank you

This package would not exist without the help of: