Using Webpack for API development!
The Spider
Posted on December 9, 2019
Looking for the example with Webpack and Apollo Server? Here's the Example repo on Github
I just want to share this rather confusing fact. Webpack is 'not' a server. It's a development tool meant to create bundles. It 'packs' web stuff...
In other words. You use webpack to build your app into something that can be ran by your Node version of choice or by a browser. You are the builder, Webpack is your conveyor belt and at the and of that tool chain will be an executable that can be started by the node
command or a tool like nodemon.
Webpack works as follows:
- You create a file with a module (file that exports a function or class)
- Webpack detects the module
- Webpack transforms this module into the format of your choice
- Webpack then adds this module into (typically) one javascript file called a "bundle". It's even called bundle.js in most cases
If you use Babel, Webpack can then transform es7 style Javascript and make it compatible for people that use older versions of Javascript.
What is the webpack-dev-server?
Webpack dev server is in fact a simple 'server' that is pre-configured to serve your bundle during development. This is nice, because it allows you to develop an app quickly using stuff like Hot Module Reloading (HMR). However, it is 'not' meant to be a tool for developing an API or any Backend App. Here's my reasoning:
The webpack-dev-server is in fact a simple Express server that uses the webpack-dev-middleware under the hood. Whenever you start it, it will run Webpack in 'watch' mode. This means that every change you make to your source code, will make Webpack transform this source and serve it for any browsers. This means that it's not only taking care of the conveyor belt, but it's also acting as a server that serves the bundle to a browser.
Note that webpack-dev-server serves separately transformed modules (the modules are not being bundled)
What is Hot Module Reloading?
The HMR principle works a bit different then the default Webpack bundler. Instead of creating a new bundle every time you make a change, it only transforms the modules, but keeps them as separate modules. Webpack-dev-server then serves these modules for your browser.
The webpack-dev-server runs a small layer of code called the HMR runtime. This runtime is connected via a websocket. This websocket is a realtime connection between your browser and your development server. Whenever your modules change on the server, they will be pushed to the browser. The runtime will then replace that module without reloading the entire browser.
It should speak for itself that this runtime and its socket connection should not be part of your production app and should therefore only run in development mode.
Using Webpack for server or backend only
People tend to think that since Webpack creates bundles, its most suitable for regular apps and not so much for APIs. That's mostly true, but it can be very useful for backends too! Should you do it? It depends.
What's certain is that since this whole HMR Runtime functionality and browser stuff is not applicable to API development, you don't need webpack-dev-server. It's overkill for API development and only makes your setup more complex, but Webpack could still be a must have!
When to use Webpack for your API's
Like I said. Webpack is a 'build' or 'transform' tool. You don't need it for development if you can run and reload your app easily using a tool like Nodemon. At some point in time you need to run your API on some server though and that's where you want to use Webpack. Here's my take on when you should and shouldn't.
If you simply require a reload of your API code whenever you make a change, then don't use Webpack for development. If you for example only require some Babel transforms, then simply use Nodemon in combination with a .babelrc file.
The grey area starts when you need to configure more tools. For example, if you want to use Typescript. You could use the 'tsc' command in watch mode, but as soon as you need to combine Babel and Typescript, it might be time to switch to Webpack.
For me the clear boundary starts from when you need to include non-javascript files like graphql or SVG files and need to combine more then 2 transformers.
In fact, whenever I build an Apollo Server API, my first choice would be using Webpack with Nodemon.
A final development setup would be something like this for your Apollo Server of Express API:
Simplifying the flow
We now have 2 processes that we need to start for 1 app. The Webpack watcher and the Nodemon process. To simplify this a bit I'm often using npm-run-all like below in my package.json:
"scripts": {
"dev": "npm-run-all -p watch:src watch:dist",
"watch:src": "webpack --config webpack.development.js",
"watch:dist": "nodemon ./dist/bundle.js",
"build": "webpack --config webpack.production.js"
}
Running npm run dev
will make npm-run-all start both the Webpack watcher and the Nodemon script. The final production build is ofcourse just the webpack script. I've also split up the Webpack configuration files for production and development and a file for common configurations like this:
./webpack.common.js
./webpack.development.js
./webpack.production.js
This is how the files look like:
webpack.common.js
Note that I've included the webpack-graphql-loader. This allows me to have separate graphql files.
const path = require('path');
module.exports = {
module: {
rules: [
{ test: /\.graphql|\.gql$/, loader: 'webpack-graphql-loader' }
]
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js'
},
resolve: {
extensions: ['.js']
},
target: 'node'
};
webpack.development.js
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const merge = require('webpack-merge');
const nodeExternals = require('webpack-node-externals');
const webpack = require('webpack');
const common = require('./webpack.common.js');
module.exports = merge.smart(common, {
mode: 'development',
watch: true,
entry: {
api: './src/main.js'
},
externals: [
nodeExternals({
whitelist: ['webpack/hot/poll?1000']
})
],
plugins: [
new CleanWebpackPlugin(),
new webpack.HotModuleReplacementPlugin()
]
});
Also an important note is that you will need to configure Nodemon to only listen for changes on ./dist/bundle.js
. This prevents unnecessary reloads. You can do that with a nodemon.json in your root:
{
"watch": ["dist/bundle.js"]
}
Now whenever you need to deploy, the below configuration would be suitable for production. You could deploy it to your Kubernetes and simply start the ./dist/bundle.js
or combine this setup with for example the Serverless framework to run it on AWS Lambda, Azure or Google Cloud.
webpack.production.js
const CleanWebpackPlugin = require('clean-webpack-plugin')
const merge = require('webpack-merge')
const nodeExternals = require('webpack-node-externals')
const path = require('path')
const common = require('./webpack.common.js')
module.exports = merge(common, {
devtool: 'source-map',
entry: [path.join(__dirname, 'src/main.js')],
externals: [nodeExternals({})],
mode: 'production',
plugins: [new CleanWebpackPlugin()]
})
That's it. I can now simply create a ./src
file and build my application by simply following the Apollo Server documentation!
Again: Here's the Webpack Apollo Server example repository.
Conclusion
Webpack is a powerful tool that can be used for both app and API development, but it's easy to drown in its feature set tricking you into thinking that it's more like for example a Node server. When that happens, remember what Webpack actually is - A very powerful and pluggable conveyor belt that 'packs' your app.
In later articles I will focus more on the actual setup side of a project and how you could structure your project to make it simple, but very scalable using patterns like the above Webpack setup.
Posted on December 9, 2019
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.