Web Development with Vite, Vue, and Flask
Tags: flask, vue, vite, docker
Published: 2023-12-07 00:18:22

**Introduction**

We'll explore how to use Vue.js and Flask together to create an application. We'll cover the process of creating a front-end template using the development tool Vite and subsequently integrating a Flask back-end. This process is quite engaging, and I believe you'll find it intriguing!

**Benefits**
Combining Vue.js for the front-end and Flask for the back-end in web application development offers several advantages, especially when leveraging their respective strengths.

Python excels in handling backend operations, data manipulation, and complex mathematical calculations. Its vast ecosystem of libraries allows for easy integration with databases, data processing tools, and machine learning frameworks.

Vue.js, a progressive JavaScript framework, is designed to build modern, interactive user interfaces. JavaScript, being the language of the web, is well-suited for client-side development. Vue.js simplifies the creation of dynamic, responsive, and visually appealing UIs. 


**Overview**

1. Creating a Vue front-end app template with Vite
2. Integrating with Flask
3. Creating a Docker image


**Creating Vue app template**

Navigate to your development directory (e.g., `Projects`) and execute the following command:

`yarn create vite`

During the setup, you'll be prompted to answer several questions:


```
Project name: vite-project (can be any name, for example: 'vite-project')
Select a framework: Vue
Select a variant: JavaScript
```

A directory named `vite-project` will be created. Navigate into it and run the `yarn` command:

```
cd vite-project
yarn
```

At this point, you can already run the template application with:

`yarn dev`

When you visit http://localhost:5173 in a web browser, you'll see the application's interface. There's a button in the center of the screen labeled "count is 0." Clicking this button will increment the count.

Next, build the app.

`yarn build`

The source will be compiled, and the artifacts will be placed in the `dist` distribution directory. To perform a final check of the application, execute:

`yarn preview`

Accessing http://localhost:4173 in a browser, you should see the application interface. The app's behavior will be identical to what was seen with 'yarn dev', but with the compiled JavaScript running the show.

The typical front-end creation process involves modifying the source code, checking its functionality with `yarn dev`, and if there are no issues, executing `yarn build`. The final operation is verified with `yarn preview`, and this cycle continues.

**Working with Flask**

Initially, let's use Flask simply to display the application's interface. Flask defines where to place different types of resources, necessitating file placement according to these rules:


| Parameter Name    | Description                                                         | Default    |
| --------------- | ------------------------------------------------------------ | --------- |
| static_folder   | JS, CSS, Location of static resources such as images                      | static    |
| template_folder | Location of HTML files to be rendered by Flask | templates |
| static_url_path | Path name for accessing the static folder                      | /static   |

Comparing the actual artifacts in the `dist` folder layout with the default values, some differences are apparent. Specifically, JS and CSS files are placed in the `dist/assets` folder, but we aim to align the layout with Flask's requirements by renaming `assets` to `static` in the configuration.

To do this, open the `vite.config.js` file and add the following setting to `build.assetsDir`:

```
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [vue()],

  build: {
    assetsDir: "static",
  },
});
```

After running `yarn build`, confirm that the output has changed to `dist/static`.

Now, Flask takes the stage:

```
from flask import Flask, render_template

app = Flask(__name__, static_folder="dist/static", template_folder="dist", static_url_path="/static")


@app.route("/", defaults={"path": ""})
@app.route("/<path:path>")
def index(path):
    return render_template("index.html")
```

Due to the layout generated by Vite differing from Flask's default, parameters like `static_folder` are explicitly specified with actual values.

Run the Flask server with:

`flask run`

Accessing http://localhost:5000 in a browser should display the application interface. The Vite logo might not appear, but the count-up functionality should work fine.

The logo's absence is due to the image file's location. If you check the `dist` folder, `vite.svg` is found directly under `dist`, but as per the table, it needs to be in the `static_folder` (=`dist/static`).

To address this, move `vite.svg` from the `public` directory to `static`:

```
cd public
mkdir static
mv vite.svg static
```

The reason for not manually handling files under `dist` is to ensure they are automatically placed during `yarn build`.

Files in the `public` directory are copied to the `dist` directory, maintaining the tree structure during the build.

Additionally, the source referencing `vite.svg` must be updated. Relevant locations include:


```index.html
<link rel="icon" type="image/svg+xml" href="/static/vite.svg" />
```

```src/App.vue
<img src="/static/vite.svg" class="logo" alt="Vite logo" />
```

After running `yarn build`, `vite.svg` should be in `dist/static`. Re-run `flask run` and visit http://localhost:5000. This time, the logo should display correctly!


To further utilize Flask, let's have it handle the incrementing process. Add to `app.py`:

```
@app.route("/increment/<int:count>")
def increment(count):
    return f"{count + 1}"
```

Modify the Vue side to request Flask to increment when the button is pressed. The relevant code is in 
`src/components/HelloWorld.vue`

```
<div class="card">
  <button type="button" @click="count++">count is {{ count }}</button>
  <p>
    Edit
    <code>components/HelloWorld.vue</code> to test HMR
  </p>
</div>
```

Replace `@click=...` with a function that requests Flask. Implement 'incrementCount' as follows:

```
const count = ref(0);

const incrementCount = () => {
  fetch(`/increment/${count.value}`)
    .then((response) => {
      if (response.ok) {
        return response.text();
      } else {
        throw new Error(`${response.status} ${response.statusText}`);
      }
    })
    .then((val) => {
      count.value = parseInt(val);
    })
    .catch((error) => {
      console.error(`Error: ${error.message}`);
    });
};
```

Substitute the button press action with this function name:


```
<div class="card">
  <button type="button" @click="incrementCount">count is {{ count }}</button>
  <p>
    Edit
    <code>components/HelloWorld.vue</code> to test HMR
  </p>
</div>
```


Verify operation with:

```
yarn build
flask run
```


**Creating a Docker Image**

Having completed most of our objectives, let's also try creating a Docker image.

Use VSCode convenient feature to create a Dockerfile:

Open the folder vite-project in VScode.
(I assume you already have it open...)

Access the command palette (Ctrl+Shift+P) and search for 'Add Docker Files'.

Select `Docker: Add Docker Files to Workspace...`. (If it doesn't appear, you might need to install the Docker extension).

`Select Application Platform`.
→ `Python Flask`.

`Choose the app's entry point(e.g. manage.py, app.py)`
→ Choose `app.py`.

`What port(s) does your app listen on? Enter a comma-separated list, or empty for no exposed port.`
→ Enter a comma-separated list, or empty for no exposed port. Select the displayed number as it is. It is probably 5002, but you can change it to any number you like.

`Include optional Docker Compose files`.
→ `Do you want to create docker-compose.yml as well?` Select `Yes`.


**Dockerfile Editing**

Make the following changes to the `Dockerfile`:

Originally, we have:

```
WORKDIR /app
COPY . /app
```

Change the `COPY` source to `./dist` since we want the `dist` directory in the container. Also, move `index.html` from the `dist` directory to the `templates` directory to adhere to the standard Flask structure:

```
WORKDIR /app
COPY ./dist/ .
RUN mkdir -p templates && mv index.html templates
```

**Modifying and Placing app.py**

In the Docker version, the Flask layout has been reverted to default, so the optional parameters specified in `app.py` should be removed:

```
app = Flask(__name__, static_folder="dist/static", template_folder="dist", static_url_path="/static")
```

In Docker, this can be simplified to:

```
app = Flask(__name__)
```

Copy `app.py` to the `public` directory and run `yarn build`:

```
cp app.py public
yarn build
```

Build the Docker image again with:

```
docker-compose build
```

Launch the container with:

```
docker-compose up -d
```

Go to http://localhost:5002.

Did it work?

Visit http://localhost:5002 and check if the app works correctly!

Finally, to avoid managing separate versions of `app.py` for Docker and non-Docker environments, consider using a configuration file to set parameters like `static_folder` based on the environment. This approach is simple yet effective.

The final `app.py` could look like this:


```
import json
from flask import Flask, render_template

args = dict()
try:
    with open("args.json") as f:
        args = json.load(f)
except FileNotFoundError:
    pass

app = Flask(__name__, **args)


@app.route("/", defaults={"path": ""})
@app.route("/<path:path>")
def index(path):
    return render_template("index.html")


@app.route("/increment/<int:count>")
def increment(count):
    return f"{count + 1}"
```


Create an `args.json` file with the following contents:

`args.json`

```
{
  "static_folder": "dist/static",
  "template_folder": "dist",
  "static_url_path": "/static"
}
```

By doing this, parameters can be environment-specific, and `app.py` can be centrally managed. If `args.json` is missing, Flask will catch the FileNotFoundError and default values will be used.