Deploying a Flask App with NPM modules on Heroku
By: Richard So
Tags: JavaScript, Web Development, Heroku, Python, Tutorial

Introduction

Let’s say you are making a website with a Flask backend and suddenly you find that it’s necessary to use a JavaScript package/module for your project. Maybe you’re doing this to give your website a bit more interaction, or you’re working with a module that’s not available as a CDN. That leaves you with the option of using the Node Package Manager (NPM). Normally, you would cast npm init on the root directory of your web development project and install your wanted modules with npm install <package> , if it weren’t for Flask (just kidding- Flask is awesome in so many different ways).

If you tried doing so, you’ll ultimately fail with unresolvable JavaScript errors, or the HTML not linking properly with your NPM module. Jokes aside, the pressing question still remains: how might one go about this? This article aims to answer this quandry.

Important Note: This short tutorial assumes that you’re familar with Flask, NPM, and Heroku, and already have a Flask app deployed on Heroku.

The Trick

If you’re familiar with Flask, you should know that a bare-bones directory structure for a (very) simple web app should look something like this:

/<your_flask_proj_directory>/

├── app.py (or __init__.py)

├── requirements.txt

├── venv (python_environment)

│ └── ...

├── static

│ ├── css

│ │ └── styles.css

│ └── img

│ ├── img1.jpg

│ └── img2.png

└── templates

├── layout.html

├── index.html

└── about.html

The key to this dilemma is to use your /static folder. It’s simple: run npm init on your static folder and install your NPM modules on that directory.

Why? Remember, the static folder is used to store files that are literally static (non-changing), unlike the HTML templates — hence the name “static”. CSS files, images, and more importantly, JavaScript code fit this criteria for a Flask app. On the other hand, files from outside the static folder won’t work properly if you try to link them with the <script> tag in your HTML templates.

Your node_modules should appear inside your static folder. At that point, you are able to link an NPM module into your HTML templates using a <script> tag. For instance, if I was trying to use swup.js to add snazzy page transitions to my site, I’ll link that module to my layout HTML like this:

<script src="{{ url_for('static', filename='node_modules/swup/dist/swup.js' }}"></script>

We use the url_for() function to create the path without needing to worry about trying to get the right relative path. Now you can use your NPM modules in your app and see them working in your local server!

Important Note: Your JavaScript file(s) in static won’t support import or require() because Flask treats it as entirely client-side JavaScript. So, you should treat your module like it’s being linked as a CDN. Refer to the documentation of the module you’re using (since every module is different) and follow the CDN track.

Oh, Heroku!

You might be thinking “It works in my setup so surely it’ll work once I deploy it onto Heroku!” This is a naive train of thought, but I’ll admit I thought the same not long ago. At this state, your installed NPM modules on static will NOT work out of the box when deploying on Heroku.

What’s worse is that Node.js or NPM is not installed if you try running with your Procfile. That’s because the original Buildpack you were using on Heroku prepares only the bare minimum of what you need to deploy your app; the Python buildpack won’t waste storage space trying to install Node.js, and vice versa. What we need is two buildpacks for this Heroku project: Node.js and Python (both officially supported in this order listed).

Assuming that you already have the Python buildpack, run the command on the root directory of your project below:

heroku buildpacks:add --index 1 heroku/nodejs

This adds the Node.js buildpack in front of the Python counterpart when building the app. This is to make sure all our NPM module assets are installed and good to go before setting up the Flask server and running it. If you do not have the Heroku CLI, you are able to configure in your Heroku settings page for your project/app.

There’s one little problem left that we have to take care of: the Node.js buildpack only searches for package.json at the root level of the project directory, meaning it won’t actually install the modules in the static folder. A simple fix is to create another package.json file at the root of your project with a postinstall entry that points to script and runs npm install there. This is what your root-level package.json should include:

"scripts": {

"postinstall": "cd static && npm install"

},

With that, your Heroku-deployed Flask app with NPM modules is complete and functional!

Conclusion

This problem bugged me not so long ago when I had to maintain an organization website with a Flask backend. Both myself and the organization thought of using page transitions with swup.js to make for a better user experience. However, swup with the plugins I needed only came as NPM modules (so CDNs are out of the picture). This article is a culmination of my findings for tackling a pretty niche use case scenario but I hope this was helpful — or at least interesting — for those of you reading this article.