VertStack is a lightweight framework for building modular, real-time web applications with a vertical architecture. It allows developers to create interconnected client-server modules that communicate seamlessly across different parts of the application. Think of it as a microservice architecture without having to manage servers.
- Modular architecture for easy scaling and maintenance
- Real-time communication between client and server
- Cross-module messaging system
- Automatic session management
- Easy-to-use API for both client and server-side code
- Cross-channel communication
- Local message handling for improved performance
- Private messaging for secure communication
- Static file serving for project directories
- Custom
index.html
support with module placement - Automatic URL rewriting for relative paths
- Iframe-based module rendering with automatic resizing
- WebSocket connection with automatic reconnection
- Node.js (version 20 or higher recommended)
- A modern web browser
- Clone the repository or copy the
VertStack
file into your project directory. - Create a directory for each module of your application.
your-project/
├── VertStack
├── index.html (optional)
├── module1/
│ ├── client.js
│ ├── server.js
│ └── public/ or dist/ (optional)
│ ├── index.html
│ └── ... (other static files)
├── module2/
│ ├── client.js
│ ├── server.js
│ └── public/ or dist/ (optional)
└── ...
Run the following command from your project root:
npx VertStack --port=3456 module1 module2 ...
Replace module1
, module2
, etc., with the names of your module directories.
You can install VertStack locally in your project directory by using the --install
flag. This process will:
- Download the current version of VertStack
- Create
watch
,serve
, andserve-down
scripts in your directory
To install:
npx VertStack --install [options] module1 module2 ...
Once installed, you can use the following scripts:
watch.cmd
(Windows) orwatch.sh
(Unix): Starts the server with auto-restart on file changesserve.cmd
(Windows) orserve.sh
(Unix): Runs the server in detached modeserve-down.cmd
(Windows) orserve-down.sh
(Unix): Stops the detached server
The serve
script automatically runs in detached mode and redirects output to a vertstack.logs
file in your project's root directory.
Example usage:
# Install VertStack locally with custom port and modules
npx VertStack --install --port=3456 module1 module2
# Start the server with auto-restart (development mode)
./watch.cmd # or ./watch.sh on Unix systems
# Start the server in detached mode
./serve.cmd # or ./serve.sh on Unix systems
# Stop the detached server
./serve-down.cmd # or ./serve-down.sh on Unix systems
After installation, you don't need to specify the modules again when running the scripts. The installation process saves your configuration for future use.
key
(string): The event key. Use dot notation for namespacing.data
(any): The data to send with the event.callback
(function): A function to handle incoming events.
- Use
_
prefix for private messages (e.g.,'_privateEvent'
) - Use
*
for project-wide events (e.g.,'*.globalEvent'
) - Use
#
prefix for cross-channel communication (e.g.,'#otherModule.event'
) - Use
$
prefix for local messages (e.g.,'$localEvent'
)
Export a function that takes bus
and sessionId
as parameters and optionally returns a cleanup function.
VertStack automatically serves static files from the public/
or dist/
directory within each module. This is useful for serving HTML, CSS, JavaScript, and other assets specific to each module.
- Create a
public/
ordist/
directory in your module folder. - Place your static files (e.g.,
index.html
, CSS, JavaScript) in this directory. - These files will be automatically served when accessing your module's route.
Example structure:
module1/
├── server.js
├── client.js
└── public/
├── index.html
├── styles.css
└── script.js
You can provide a custom index.html
file in the root of your project to control the overall layout of your application.
- Create an
index.html
file in your project root. - Use the special comment syntax
<!-- @[MODULENAME] -->
to indicate where each module should be rendered.
Example index.html
:
<!DOCTYPE html>
<html>
<head>
<title>My VertStack Application</title>
</head>
<body>
<header>Welcome to My App</header>
<main>
<section id="module1">
<!-- @module1 -->
</section>
<section id="module2">
<!-- @module2 -->
</section>
</main>
<footer>© 2024 My VertStack Application</footer>
</body>
</html>
VertStack automatically rewrites relative URLs within each module to ensure they work correctly when served as part of the larger application.
This feature works for:
- HTML
src
andhref
attributes - CSS
url()
functions - Dynamically added elements and styles
No additional configuration is needed; this happens automatically for all modules.
Modules are rendered within iframes, which are automatically resized to fit their content. This ensures that each module displays correctly regardless of its content size.
- Each module's content is wrapped in an iframe.
- A script monitors the content size of each iframe.
- The iframe's height is dynamically adjusted to match its content.
No additional configuration is required; this feature works automatically for all modules.
Examples can be found under the examples folder in Github.
The #
prefix allows you to send messages to specific channels (modules) from any other module. This is useful for inter-module communication without broadcasting to all modules. By default exported functions will subscribe to private and global scope.
// Send a message to the 'userStats' module
bus("#userStats.update", { activeUsers: 10 });
// userStats exports a handler
export const update = (payload) => {
// handle the event
}
The $
prefix is used for local messages that should not be sent over the network. This is useful for optimizing performance and keeping certain logic contained within either the client or server side.
// Send a local message (client-side or server-side)
bus("$localEvent", { someData: "value" });
// or
bus("$local-event", { someData: "value" });
export const $localEvent = (payload) => {
// Handle the local event
};
The _
prefix is used for server to client messages in a single module. If prefixed with _ only the server or client module will receive the message.
// Send a vertical message
bus("_verticalMessage", { sensitiveData: "value" }, targetSessionId);
// Listen for vertical messages
export const _verticalMessage = (payload) => {
// Handle the private message
console.log("Received private message:", payload.data);
};
As a server can handle multiple clients it is configured slightly different. It expects a default export that can handle new sessions.
export default (bus, sessionId, pageId) => {
console.log('new user', sessionId, pageId)
return () => {
console.log('called when user leaves');
}
}
In the event that you want to subscribe to all public messages for the session you can use the wildcard *
prefix directly on the bus.
export default (bus, sessionId, pageId) => {
bus('*', (payload) => {
console.log('Received message', payload);
});
bus('*.targetedMessage', (payload) => {
console.log('Received message with .targetedMessage as subkek and the payload', payload);
});
}
VertStack supports proxying requests to specific ports for each module. This feature is useful when you need to integrate existing services or APIs with your VertStack application.
When starting the server, you can specify a proxy port for each module:
npx VertStack module1=8080 module2=8081
This will set up module1 to proxy requests to port 8080 and module2 to port 8081.
- Requests to
/{moduleName}/p/{path}
will be proxied to the specified port. - The proxy will forward all headers and the request body.
- This allows you to seamlessly integrate existing services with your VertStack application.
Example: If you have an existing API running on port 8080, you can integrate it with your VertStack module like this:
// In your client.js
async function fetchData() {
const response = await fetch('/module1/p/api/data');
const data = await response.json();
// Process the data
}
This request will be proxied to http://localhost:8080/api/data
.
The concept of pageId
can be used to manage multiple instances of the same module across different pages or components of a user session.
The pageId
is automatically generated and managed by VertStack. In your module code, you can access and use it as follows:
- In server-side code:
module.exports = function (bus, sessionId, pageId) {
bus('someEvent', (payload) => {
console.log(`Received event for page: ${pageId}`);
// Handle the event
});
// Send a message to a specific page instance
bus('specificPageEvent', data, sessionId, pageId);
};
- In client-side code:
// The pageId is automatically handled in the background
bus('someEvent', (payload) => {
// This will only be called for events sent to this specific page instance
console.log('Received event:', payload);
});
- If modules aren't loading, ensure the directory names match the command-line arguments.
- Check the console for WebSocket connection errors if real-time updates aren't working.
- Verify that event keys are correctly namespaced to avoid conflicts between modules.
- If static files are not being served, check that they are placed in the
public/
ordist/
directory of the module. - For custom layouts, ensure your root
index.html
file uses the correct<!-- @[MODULENAME] -->
syntax for module placement.
Contributions are welcome! Please submit pull requests or open issues for any bugs or feature requests.
This project is licensed under the MIT License.