npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

instant-express-core

v0.0.2

Published

Core [`instant-express`](https://github.com/cjmyles/instant-express) functionality, including routing, authentication, session, firebase connectivity and utilities.

Downloads

3

Readme

Instant Express Core

Core instant-express functionality, including routing, authentication, session, firebase connectivity and utilities.

Please note: this module contains some code that will assist with the creation of a Firebase Functions application. However, you can easily use this module for any Express app, and other database/hosting drivers will be added in the future.

Features

  • Basic Authentication
  • CORS Middleware
  • Winston Logging
  • Automatic Routing
  • Session
  • Firebase Admin initialisation
  • Generic Actions and Controller classes that can be easily extented to enable simple CRUD routing operations
  • Firebase Firestore specific Repository class to enable retrieval of Firestore collections and documents

Sample Usage

const express = require('express');
const InstantAPI = require('instant-express-api');

const app = express();
// const config = ...
const instant = InstantAPI.initialize(config);

// Create our app
const app = express();

// Initialise InstantAPI
const instant = InstantAPI.initialize(config);

// Set up CORS
app.use(instant.cors());

// Set up auth
app.use(instant.auth());

// Enable logging
app.logger = instant.logging();
// or, `logger = instant.logging();` if you want a global variable

// Set up firebase
app.firebase = instant.firebase();
// or, `firebase = instant.firebase();` if you want a global variable

// Set up the routes
app.use(instant.routes());

Configuration

Some features require a configuration object. Options should be passed in on initialisation as a json object. I recommend using something like config to access your configuration settings, but you could equally implement something simpler.

Example

"auth": {
    "method": "basic",
    "users": {
        "craig": "pa55w0rd",
        "admin": "supersecret",
    }
},
"logging": {
    "error": {
        "filename": "error.log"
    },
    "combined": {
        "filename": "combined.log"
    }
},
"routes": {
    "path": "api/routes"
},

Authentication

Basic

Utilises Express Basic Auth.

Sample Usage

app.use(
  instant.auth({
    method: 'basic',
    users: {
      craig: 'pa55w0rd',
      admin: 'supersecret',
    },
  })
);

Options

| Name | Type | Description | Options | | :------- | :------- | :-------------------------------- | :------ | | method | string | Authentication type | "basic" | | users | object | username:password key value pairs |

CORS

Utilises CORS.

Sample usage

app.use(instant.cors());

Options

N/A

Logging

Utilises Winston.

Sample usage

app.logger = instant.logging({
  error: {
    filename: 'error.log',
  },
  combined: {
    filename: 'combined.log',
  },
});

Options

| Name | Type | Description | Options | | :------------------ | :------- | :--------------------------- | :------ | | error.filename | string | File to log errors to | | combined.filename | string | File to log combined logs to |

Routes

Autoconfigure API routes (rather than manually importing each routes file one by one). Can be configured to utilise routes files from one directory (e.g. ./api/routes/), or within base directories (useful for versioned routes such as /api/v1.0/routes).

For example, we can expose a GET method on /api/test/ assuming we've have ./api/routes/test.js that looks something like this:

// ./api/routes/test.js
import express from 'express';

const router = express.Router();

router.get('/', (req, res) => {
  res.send({ testing: 123 });
});

module.exports = router;

Sample usage

app.use(
  instant.routes({
    path: 'api/routes',
  })
);

Options

| Name | Type | Description | Options | | :---------------- | :------- | :--------------------------------------------- | :------ | | base (optional) | string | Location of base API directory, e.g. api | | path | string | Location of API routes directory, e.g routes | | prefix | string | Prefix to apply to routes, e.g. api |

Note: you can have multiple named directories that sit between the base and path, all of which will be automatically parsed, for example ./api/v1.0/routes/test.js and ./api/v2.0/routes/test.js.

Session

Utilises Express Session or Cookie Session.

Sample usage

app.use(instant.session({
  "type": "express",
  "options": {
    "secret": "super-secret-phrase",
    "resave": false,
    "saveUninitialized": false,
    "cookie": {}
  }
});

Options

| Name | Type | Description | Options | | :-------- | :------- | :------------------------------------------------------------------ | :------------------ | | type | string | Session type | express, cookie | | options | object | Module specific options (see specific package for more information) |

Firebase

Initialises our Firebase app and exposes the following services:

  • Auth (coming soon)
  • Firestore
  • Storage (coming soon)

Sample usage

app.use(instant.firebase({
  'serviceAccountKey': 'config/serviceAccountKey.json'
});

Options

| Name | Type | Description | Options | | :------------------ | :------- | :-------------------------------------------------------------------------------------------------- | :------ | | serviceAccountKey | string | Location of Service Account Key JSON file (please see https://firebase.google.com/docs/admin/setup) |

Classes

Writing entity specific routes can be time consuming, especially when you have a lot of routing code replicated across multiple entities performing the same task. For example, imagine you have two API routes, /api/clients and /api/users that both have create, find all, find one, find by id, update and delete operations. The majority of the code will be same across both routes, performing functions such as fetching from the database, testing for the existence of a returned record or document, and handling the response.

To alleviate this issue, this module includes classes that can perfom these basic tasks, which you can extend from for your own needs. However, this would assume the following code structure:

/api
    /routes
        /test.js
    /actions
        /test.js
    /controllers
        /test.js
    /repositories
        /test.js
  • A routes method defines which actions method to execute for a given request.
  • An actions method defines which controller method to execute.
  • A controller method defines which repository method to execute.
  • A repository method communicates with a database and returns the results.

Data is passed back from the repository to the action which handles the response. This might seem over elaborate, but provides a clear separation of concerns between the tasks being performed. It also provides the following benefits:

  • It would be relatively simple to swap one repository out for another. For example, as long as the same methods existed, we could swap between using a Mongo db and Firestore.
  • Although an actions could communicate directly with respositories, controllers enable us to perform calculations or operations on the results before they are returned. This means that controllers could reference other controllers and the calculations or operations happen in one place. It also separates what might be send in a request from the required inputs for retrieving or calculating data.

Note: although not specifically referenced here, I would suggest also having an adapters directory for handling operations to other 3rd party APIs (such as Twilio or Sendgrid), as well as a helpers directory for common entity specific utilities such as validators or formatters.

The following operations are currently supported:

  • create
  • createMany
  • find
  • findOne
  • findById
  • update
  • updateOrCreate
  • delete

Actions

| Method | Expected request | Type | Description | Example | | :--------------- | :------------------------- | :------- | :---------------------------------------- | :----------------------- | | create | req.body | object | Attributes to create document with | { "name": "Craig" } | | | req.params.id (optional) | string | Id to create document with | "00001" | | find | req.query | string | Search query | "name=Craig" | | findOne | req.query | string | Search query | "name=Craig" | | findById | req.params.id | string | Document id | "00001" | | update | req.body | object | Attributes to update document with | { "location": "Sydney" } | | updateOrCreate | req.query | string | Search query | "name=Craig" | | | req.body | object | Attributes to update/create document with | { "location": "Sydney" } | | delete | req.params.id | string | Document id | "00001" |

Sample usage

// ./api/routes/clients.js
const express = require('express');
const actions = require('../actions/clients');

const router = express.Router();

router.post('/', actions.create);
router.post('/:id', actions.create);
router.get('/', actions.find);
router.get('/:id', actions.findById);
router.put('/:id', actions.update);
router.delete('/:id', actions.delete);

module.exports = router;
// ./api/actions/clients.js
const Actions = require('instant-express-api').Actions;
const controller = require('../controllers/clients');
// const HttpStatusCodes = require('http-status-codes');

class Clients extends Actions {
  // Includes the following methods by default:
  // create, createWithId, createMany, find, findOne, findById, update, updateOrCreate and delete
  //
  // e.g.
  //   async create(req, res, next) {
  //     try {
  //       let response = req.params.id
  //         ? await this.controller.createWithId(req.params.id, req.body)
  //         : await this.controller.create(req.body);
  //       res.status(HttpStatusCodes.OK).send(response);
  //     } catch (error) {
  //       res.status(HttpStatusCodes.OK).send(error);
  //     }
  //   }
  //
  // Custom methods here...
  // Note: we have to bind the method to `this`
  //
  // e.g.
  //   constructor(controller) {
  //     this.custom = this.custom.bind(this);
  //     super(controller);
  //   }
  //
  //   async custom(req, res, next) {
  //     try {
  //       let response = await this.controller.custom(req.params.id)
  //       res.status(HttpStatusCodes.OK).send(response);
  //     } catch (error) {
  //       res.status(HttpStatusCodes.OK).send(error);
  //     }
  //   }
}

module.exports = new Clients(controller);
// api/controllers/clients.js
const Controller = require('instant-express-api').Controller;
const repository = require('../repositories/clients');

class Clients extends Controller {
  // Includes the following methods by default:
  // create, createWithId, createMany, find, findOne, findById, update, updateOrCreate and delete
  //
  // e.g.
  // async create(attributes) {
  //     try {
  //       return await this.repository.create(attributes);
  //     } catch (error) {
  //       throw error;
  //     }
  //   }
  //
  // Custom methods here...
  // Note: we have to bind the method to `this`
  //
  // e.g.
  //   constructor(repository) {
  //     this.custom = this.custom.bind(this);
  //     super(repository);
  //   }
  //
  //   async custom(id) {
  //     try {
  //       return await this.repository.custom(id)
  //     } catch (error) {
  //       res.status(HttpStatusCodes.OK).send(error);
  //     }
  //   }
}

module.exports = new Clients(repository);
// api/repositories/clients.js
const FirestoreRepository = require('instant-express-api').FirestoreRepository;
const firebase = require('../../firebase'); // Your firestore initialisation script (see below)

class Clients extends FirestoreRepository {
  // Includes the following methods by default:
  // create, createWithId, createMany, find, findOne, findById, update, updateOrCreate and delete
  //
  // e.g.
  //   async create(attributes) {
  //     try {
  //       const ref = await this.db.collection(this.collection).add(attributes);
  //       if (ref.id) {
  //         return await this.findById(ref.id);
  //       }
  //     } catch (error) {
  //       throw error;
  //     }
  //   }
  //
  // Custom methods here...
  // Note: we have to bind the method to `this`
  //
  // e.g.
  //   constructor(db, collection) {
  //     this.custom = this.custom.bind(this);
  //     super(db, collection);
  //   }
  //
  //   async custom(id) {
  //     try {
  //       return await this.db.collection(this.collection).get(id);
  //     } catch (error) {
  //       res.status(HttpStatusCodes.OK).send(error);
  //     }
  //   }
}

module.exports = new Clients(firebase.db, 'clients');
// firebase.js - see https://firebase.google.com/docs/admin/setup
const admin = require('firebase-admin');

const serviceAccount = require('./serviceAccountKey.json');

admin.initializeApp({
  credential: admin.credential.cert(serviceAccount),
});

const db = admin.firestore();
const settings = { /* your settings... */ timestampsInSnapshots: true };
db.settings(settings);

module.exports = { db };

Running Tests

Please note: tests haven't yet been implemented (https://auth0.com/blog/developing-npm-packages/).

To run the tests, clone the repository and install the dependencies:

git clone https://github.com/JSJInvestments/instant-express-api.git
cd instant-express-api && npm i
npm run test

License

MIT