Redux Toolkit w/ Vite in Docker

docker, Node.js, programming, react

Notes on setting up a Docker/Vite/React/Redux Toolkit service in Docker. That’s a mouthful.

Docker Files

Start with a name for the project (e.g. “myproject”). Then create these Docker files:

Dockerfile

FROM node:20.10

RUN apt update && apt install -y \
  xdg-utils

EXPOSE 5173

docker-compose.yml

version: '3'
services:
  app:
    build: .
    command: >
      sh -c "npm run dev"
    ports:
      - "5173:5173"
    expose:
      - "5173"
    volumes:
      - .:/app
    tty: true
    stdin_open: true

Now build the working image and shell into the container:

docker-compose build

Redux Toolkit Setup

Shell into a container of the working image and initialize the project:

docker-compose run --rm app /bin/bash
...
npx degit reduxjs/redux-templates/packages/vite-template-redux myproject

Of course, use the name of the project instead of myproject.

This will create the starter files for the project under myproject/.

Exit the shell.

File Updates

Dockerfile

Modify Dockerfile to build the image with the project:

FROM node:20.10

RUN apt update && apt install -y \
  xdg-utils

EXPOSE 5173

WORKDIR /app/myproject

RUN npm install -g npm 

vite.config.ts

Modify the Vite config to allow it to host from a Docker container:

import { defineConfig } from "vitest/config"
import react from "@vitejs/plugin-react"

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [react()],
  server: {
    host: true,  // <-- ADD THIS
    open: true,
  },
  ....

Start

Finally, to start the server, first build the image again:

docker-compose build

Then bring up the app:

docker-compose up

This should now bring up the app at http://localhost:5173/

To work on the project’s settings (e.g. installing packages, running tests, etc.), shell in with:

docker-compose run --rm --service-ports app /bin/bash

The --service-ports option will allow commands like npm run dev to start the app correctly (i.e. so that http://localhost:5173/ works). Without it, the port 5173 will not be mapped to the host, and docker-compose up will be the only way to run the app.

Sessions with Node.js and Express

Node.js, programming

I cannot believe how difficult it was to do something so simple. The bulk of the reason behind this, I believe, is the lack of documentation.

It always starts at App.js:

var express = require('express');
...
var app = express();
app.configure(function() {
...
app.use(express.bodyParser());
...
app.use(express.cookieParser('secret'));
...
app.use(express.session());
app.use(app.router);
...
});

...
app.get('/', routes.index);
app.get('/loginf', routes.loginf);
app.post('/login', routes.login);
app.get('/logout', routes.logout);

These set up the app to:

  • bodyParser: parse form submissions and put the parsed parameters into req.body
  • cookieParser: parse cookie values when present
  • session: session support
  • router: URL routing support
exports.index = function(req, res) {
  // If the session has a username, echo it to the view
  res.render('index', {username:req.session.username});
};

exports.loginf = function(req, res) {
  // render the login form
  res.render('loginf', {});
};

exports.login = function(req, res) {
  // extract the submitted form and record the user name
  // in the session
  var userName = req.body.username;
  var password = req.body.password;
  req.session.username = userName;
  res.redirect('/');
};

exports.logout = function(req, res) {
  // clear out the session
  req.session.destroy();
  res.redirect('/');
};

The index view (views/index.jade) either displays the current user name or offers a link to log in.

doctype 5
html
 body
  - if (typeof username != 'undefined') 
   div User #{username} 
   div 
    a(href='/logout') Log out
  - else
   div
    a(href='/loginf') Login please
html
 body
  table
   form(method='POST',action='/login')
    tr
     td
      div User Name
     td
      input(type='text',name='username')
    tr
     td
      div Password
     td
      input(type='text',name='password')
    tr
     td(colspan='2')
      input(type='submit',value='Log in')

Turns out the indentation matters in Jade, just like in Python. If A and B are in the same column, they are siblings. If B is indented further than and below A, then B is a child element of A.

Things yet to be figured out

All calls to render the ‘index’ view (or any other view that needs to know whether a user is logged in) currently needs to explicitly set the model’s “username” property. Is there an easier way? That is, can a Jade file access the session object?

Is there an interception/filter mechanism to reroute to the ‘loginf’ view if there is no “username” in the session, so that I don’t have to do this on every controller that requires a logged in user?

Turns out the answer is the various “middleware” components appended to “app” in app.js. This article describes it.

Debugging Node.js on Windows

Node.js, programming

Confluence

One annoying thing about Open Source and/or free stuff in general is that things are sparsely documented, if even. Take the n packages and, due to the ways they interact, I may have n! possible steps to integrate.

Here are the components of my environment:

  • Windows (Operating system)
  • Eclipse (IDE)
  • Chrome (Web browser)
  • Node.js (language)
  • Express (Web framework)
  • node-inspector (debugger add-in)

Fundamental prerequisites

All these “big” install are pretty much straight-forward. Note that node-inspector seems to work well only with Chrome; IE and Firefox don’t seem to work for me, possibly due to the Webkit implementations (or lack thereof).

Web app set-up

Install Express:

npm install -g express

Install node-inspector:

npm install -g node-inspector

Create web-app (short way w/ Express):

express myapp
cd myapp
npm install

Start Debugging

Installing nodeclipse plug-in

Follow the direction from the nodeclipse page to install via Eclipse Help | Install New Software …

My node.exe is installed as:

C:Program Files (x86)nodejsnode.exe

But the nodeclipse plug-in assumed that it is c:program filesnodejsnode.exe, so I had to change the location to the node.exe file via Windows | Preferences | Nodeclipse.

Then I opened the Node perspective via Windows | Open Perspective | Other … and picked Node.

From there, create a new Node project and import from myapp. Eclipse should know how to structure the resources now that the nodeclipse plugin is installed.

Load up app.js and click the debug button from the toolbar.

Start node-inspector:

Open up a command prompt

C:Program Files>node-inspector
 info - socket.io started
visit http://0.0.0.0:8080/debug?port=5858 to start debugging

Despite the message, Chrome needs to hit this instead: http://localhost:8080/debug?port=5858 (http://127.0.0.1:8080/debug?port=5858 also works; just not 0.0.0.0)

Clicking the “Scripts” toolbar button should reveal a list of JS files to open in which breakpoints can be placed.

Alternative: Debugging without Eclipse

The Eclipse IDE helps with code editing, but since node-inspector is used for debugging, Eclipse is not strictly required for debugging. To start debugging on a command prompt without Eclipse:

cd myapp
node --debug app.js

Alternative: Debugging without node-inspector

See https://github.com/joyent/node/wiki/Using-Eclipse-as-Node-Applications-Debugger

This will install the “ChromeDevTools” and “Chromium JavaScript *” plug-ins into Eclipse. Note that these should not replace the “Nodeclipse” plug-in. The former are debugging tools, and the latter is for editing and running Node.js files.

The only cumbersome part I found is that, to debug a Node.js app, there are two steps instead of one:

  • Start to debug the Node.js app itself from the Node perspective, using the “Node Application” configuration added by the Nodeclipse plug-in. (I think it also works if I use the “Chromium JavaScript” configuration.) This will set the app up to listen to localhost:5858 for debugging.
  • Now create a new debugging configuration using the “Standalone V8 VM” configuration and set the port to 5858. Start that configuration also so that it will attach to the first.

For someone from a Java background, I guess this is similar to running a Java app with “-Xdebug -Xrunjdwp:server=y, transport=dt_socket,address=5858” and then attaching a debugger to port 5858.