Note

This documentation reflects the beta version of NodePlace. The current alpha version is a pre-release, aimed at gathering feedback from the community to shape the final product and address key developer pain points.

Typescript support is only available in es modules. however, that does not mean that the code will break in commonjs modules, only that you will have to turn of noImplicitAny in tsConfig and also other fields that may cause the ts compiler to complain.

This alpha version is not intended for serious projects but to try out and explore. Please report any issues or bugs you encounter on our Discord server. Your feedback is invaluable. Thank you in advance for helping make NodePlace better!

Introduction

What is NodePlace.js?

NodePlace is a lightweight, zero-dependency Node.js framework designed for building fast, scalable server-side applications. With a strong focus on simplicity and performance, NodePlace is ideal for creating APIs, microservices, or server-driven applications. Its intuitive design makes it a perfect drop-in replacement for Express.js, sharing familiar syntax and structure while introducing additional features tailored for modern development.

Pre-Requisites

To get the most out of NodePlace, it’s recommended that you have a foundational understanding of Node.js. While not strictly required, prior experience with Express.js can accelerate the learning curve, as this framework have a similar syntax and structure.

Features

Core Features

featureDescription
RoutingIntuitive system to define how HTTP requests are handled based on URL paths and methods.
MiddlewareHandles request/response interception, enabling flexible functionality and modular design.
Error HandlingProvides robust error-catching and custom error-handler support for consistent responses.
Static File ServingEfficiently serves static files with options for caching, custom headers, and file extension fallbacks.

Additional Features

featureDescription
Zero dependency Lightweight and simple, with no external dependencies to minimize peer dependency issues.
Flexible Event SystemEasily manage custom events to streamline workflows for real-time and asynchronous tasks.
Seamless Integration with Node.jsDesigned to integrate effortlessly with the Node.js ecosystem.
Performance-Optimized ArchitectureBuilt with high performance and scalability in mind.
Comprehensive API SupportEquipped with modern HTTP utilities for JSON parsing, cookie management, and more.

Installation

Installation via NPM

To start using NodePlace, install it using your preferred package manager. We recommend using npm or yarn for simplicity:

npm install nodeplace

or if you use yarn:

yarn add nodeplace

Installation via NodePlace CLI

For users who want to get started with NodePlace alongside an existing template, either from NodePlace itself or the community, the NodePlace CLI provides a seamless solution.

Install the CLI globally using npm:

ynpm install -g @nodeplace/cli

After installation, you can create a new NodePlace application by running:

npx create-nodeplace-app

Follow the prompts to select a template from the list.

Alternatively, you can specify a template directly:

npx create-nodeplace-app --template@template-name

Note: The CLI is in alpha; feedback is appreciated to improve its functionality.

Requirements

  • Node.js 16 or higher: NodePlace leverages modern JavaScript features, so ensure your environment is up to date.
  • Package Manager: Ensure you have npm or yarn installed to manage dependencies effectively.

Hello World

Getting started with NodePlace is simple. Here's a quick example to set up a basic server:

import nodeplace from 'nodeplace'

const app = nodeplace()

app.get('/', (req, res) => {
    res.send('Hello, World!')
})

app.listen(3000, () => {
    console.log('Server is running on http://localhost:3000')
})

Explanation

  • Import the Framework:
  • Use the nodeplace package to access all the core functionalities.

  • Create an Application Instance:
  • nodeplace() initializes a new app, similar to other Node.js frameworks.

  • Define a Route:
  • The app.get() method specifies an HTTP GET handler for the root URL (/).

  • Start the Server:
  • The app.listen() method starts the server on port 3000 and listens for incoming requests.

"The req (request) and res (response) objects in NodePlace are built on the native Node.js objects, with added methods and properties to simplify common tasks. You can still use native methods like req.pipe() or req.on('data', callback) seamlessly, while benefiting from the extended capabilities provided by NodePlace."

Running the Server

  • Create a file named server.js and paste the code above into it.
  • Run the app with:
node server.js

Visit http://localhost:3000 in your browser or API testing tool (e.g., Postman or Insomnia). You should see the text "Hello, World!".

Routing

Routing determines how your application responds to client requests for specific endpoints, defined by a URI (path) and an HTTP method (GET, POST, etc.).

In NodePlace, each route can have one or more handler functions executed when the route matches an incoming request.

Here’s the basic structure of a route:

app.METHOD(PATH, HANDLER)

Where:

  • app is an instance of express.
  • METHOD is an HTTP request method, in lowercase.
  • PATH is a path on the server.
  • HANDLER is the function executed when the route is matched.

For example:

app.get('/', (req, res) => {
    res.send('Hello, World!')
})

The req (request) and res (response) objects have enhanced properties, allowing seamless access to request data and sending responses.

This tutorial assumes that an instance of express named app is created and the server is running. If you are not familiar with creating an app and starting it, see the Hello world example.

Route methods

A route method corresponds to an HTTP method. In NodePlace, you can define routes for various HTTP methods like get, post, put, etc.

// Define a GET route
app.get('/', (req, res) => {
    res.send('GET request to the homepage')
})

// Define a POST route
app.post('/', (req, res) => {
    res.send('POST request to the homepage')
})

NodePlace supports all standard HTTP methods. For instance:

Respond to a PUT request:

app.put('/user', (req, res) => {
    res.send('Got a PUT request at /user')
})

Respond to a PATCH request:

app.patch('/user', (req, res) => {
    res.send('Got a PATCH request at /user')
})

Respond to a DELETE request to the /user route:

app.delete('/user', (req, res) => {
  res.send('Got a DELETE request at /user')
})

Route Parameters

Route parameters are dynamic segments of a URL. They allow you to capture values in the URL path and make them accessible in the req.params object.

Example:

// Define a route with parameters
app.get('/users/:userId/books/:bookId', (req, res) => {
    res.send(req.params)
})

// Request URL: /users/34/books/8989
// req.params: { "userId": "34", "bookId": "8989" }

Route Handlers

You can define multiple handlers for a single route to process requests in stages:

Single handler function:

app.get('/example', (req, res) => {
    res.send('Hello from Example!')
})

Multiple handlers:

app.get('/example', (req, res, next) => {
    console.log('Handler 1 executed')
    next()
}, (req, res) => {
    res.send('Handler 2 executed')
})

Array of handlers:

const middleware1 = (req, res, next) => { console.log('Middleware 1'); next() }
const middleware2 = (req, res, next) => { console.log('Middleware 2'); next() }

app.get('/example', [middleware1, middleware2, (req, res) => {
    res.send('Hello from Example!')
}])

NodePlace Router

The Router module in NodePlace lets you modularize routes and middleware, treating them as mini-apps. This is helpful for organizing related routes.

Example:

// birds.js
import nodeplace from 'nodeplace'

const router = nodeplace.Router()

// Middleware specific to this router
router.use((req, res, next) => {
    console.log('Time:', Date.now())
    next()
})

// Define routes
router.get('/', (req, res) => {
    res.send('Birds home page')
})

router.get('/about', (req, res) => {
    res.send('About birds')
})

export default router

Mount the router in your main app:

import birds from './birds'

app.use('/birds', birds)

The app can now handle requests to /birds and /birds/about.

Using Middleware

Middleware in NodePlace is a key part of the request-response lifecycle. It allows you to execute code, make changes to request and response objects, end the request-response cycle, or pass control to the next middleware function in the stack. Middleware functions typically receive three parameters: req, res, and next.

Middleware functions can:

  • Execute any code.
  • Modify req or res objects.
  • End the request-response cycle.
  • Call the next middleware in the stack using next().

Types of Middleware

Application-Level Middleware: Application-level middleware applies to the entire application and can be mounted using app.use() or app.METHOD(). Here's an example:

Global Middleware (No Mount Path)

This middleware runs for every incoming request:

const nodeplace = require('nodeplace')
const app = nodeplace()

app.use((req, res, next) => {
    console.log('Time:', Date.now())
    next()
})

Middleware with a Mount Path

This middleware runs for requests to /user/:id:

app.use('/user/:id', (req, res, next) => {
    console.log('Request Type:', req.method)
    next()
})

Route-Level Middleware

Middleware can be tied to specific HTTP methods and routes:

app.get('/user/:id', (req, res, next) => {
    console.log('User ID:', req.params.id)
    next()
}, (req, res) => {
    res.send('User Info')
})

Middleware Sub-Stacks

You can group multiple middleware functions to create a middleware stack:

app.use('/user/:id', [
    (req, res, next) => {
        console.log('Request URL:', req.originalUrl)
        next()
    },
    (req, res, next) => {
        console.log('Request Type:', req.method)
        next()
    }
])

Writing Middleware

Creating custom middleware is straightforward in NodePlace. Middleware is simply a function that accepts req, res, and next parameters. Here's how you can write middleware.

This middleware logs request information:

const logRequest = (req, res, next) => {
    console.log(`Request URL: ${req.url}`)
    console.log(`Request Method: ${req.method}`)
    next() // Pass control to the next middleware
}

app.use(logRequest)

Middleware that Ends the Response

If the middleware completes the request-response cycle, it doesn’t need to call next():

const blockAccess = (req, res) => {
    res.status(403).send('Access Denied')
}

app.use(blockAccess)

Middleware with Parameters

Middleware can be dynamic by accepting parameters:

const setCustomHeader = (headerName, headerValue) => (req, res, next) => {
    res.setHeader(headerName, headerValue)
    next()
}

app.use(setCustomHeader('X-Custom-Header', 'MyValue'))

Error-Handling Middleware

Error-handling middleware has four parameters: (err, req, res, next):

const errorHandler = (err, req, res, next) => {
    console.error(err.stack)
    res.status(500).send('Something went wrong!')
}

app.use(errorHandler)

Middleware in NodePlace is versatile and allows you to build modular, reusable logic for handling requests. By understanding how to use and write middleware, you can easily create powerful applications that handle complex workflows with ease.

Error Handling

Error handling in NodePlace ensures your application gracefully handles unexpected issues, both synchronously and asynchronously. NodePlace provides a built-in error handler, so you don't need to start from scratch. However, customizing error handling allows for tailored responses and better debugging.

Catching Errors

Errors in your application can occur during route handling or middleware execution. NodePlace offers robust mechanisms to catch both synchronous and asynchronous errors.

Synchronous Errors

Synchronous code errors are automatically caught by NodePlace without additional configuration. For instance:

app.get('/', (req, res) => {
    throw new Error('Something went wrong!') // Automatically handled.
})

Asynchronous Errors

For asynchronous errors, explicitly pass the error to the next() function:

import fs from 'fs'

app.get('/', (req, res, next) => {
    fs.readFile('/non-existent-file', (err, data) => {
        if (err) {
            next(err) // Passes the error to the handler.
        } else {
            res.send(data)
        }
    })
})

In modern JavaScript, you can also rely on async/await for cleaner error handling:

app.get('/user/:id', async (req, res, next) => {
    try {
        const user = await getUserById(req.params.id)
        res.send(user)
    } catch (error) {
        next(error) // Automatically passes the error to NodePlace's error handler.
    }
})

Promises and Errors

Promises make error handling easier by catching both synchronous and asynchronous errors:

app.get('/', (req, res, next) => {
    Promise.resolve().then(() => {
        throw new Error('Promise error!')
    }).catch(next) // Automatically handles the error.
})

Common Patterns

You can chain middleware functions to simplify error handling in complex workflows:

app.get('/', [
    (req, res, next) => {
        fs.writeFile('/path', 'data', next) // Passes errors to the next function.
    },
    (req, res) => {
        res.send('Operation succeeded!')
    }
])

Even in asynchronous code, you can use try...catch or promise chains to ensure NodePlace receives the error:

app.get('/', (req, res, next) => {
    setTimeout(() => {
        try {
            throw new Error('Timeout error!')
        } catch (err) {
            next(err)
        }
    }, 100)
})

Writing error handlers

Error-handling middleware functions in NodePlace follow the same structure as regular middleware but must include four arguments: (err, req, res, next).

Basic Error Handler

Here's a simple error-handling middleware:

app.use((err, req, res, next) => {
    console.error(err.stack) // Logs the error.
    res.status(500).send('Something went wrong!')
})

Defining Error Handlers

Define error-handling middleware after all other middleware and routes:

app.use(bodyParser.json())
app.use(methodOverride())

// Error handler
app.use((err, req, res, next) => {
    res.status(500).json({ error: err.message })
})

Custom Error Handlers

Custom error handlers allow tailored responses:

XHR Request Error:

const clientErrorHandler = (err, req, res, next) => {
    if (req.xhr) {
        res.status(500).send({ error: 'XHR failed!' })
    } else {
        next(err)
    }
}
app.use(clientErrorHandler)

Logging Errors:

const logErrors = (err, req, res, next) => {
    console.error(err.stack)
    next(err)
}
app.use(logErrors)

Catch-All Handler:

const errorHandler = (err, req, res) => {
    res.status(500).render('error', { error: err })
}
app.use(errorHandler)

Skipping Handlers

Skip specific route handlers by passing 'route' to next():

app.get('/restricted', (req, res, next) => {
    if (!req.user.isAuthenticated) {
        next('route') // Skips to the next route handler.
    } else {
        res.send('Welcome!')
    }
})

Effective error handling is crucial for robust applications. NodePlace simplifies this with built-in support for synchronous and asynchronous errors, along with customizable error-handling middleware. Whether logging errors, handling API-specific issues, or providing user-friendly messages, NodePlace empowers you to manage errors gracefully.

Application Settings

NodePlace allows you to manage application settings using the app.settings method:

app.settings({poweredBy: false });

You can also enable or disable individual settings:

app.enable('poweredBy');
app.disable('poweredBy');