Document storage feature in Node.js

This article presents an example of an application that uses the document storage feature in Node.js and Express.js.

This example assumes that the Document Storage feature is already properly enabled. To learn how to enable document storage refer to our dedicated guide.

# Dependencies

This example uses the following dependencies:

  • axios
  • body-parser
  • cors
  • express

It also uses core dependencies from Node.js: path and fs.

# Example

The following example allows you to upload an editor bundle, import a document to the storage, get a list of documents, get a document, and remove a document.

This file presents an example of a simple Express.js application.

Remember to provide a correct API secret and generate a proper request signature.

// index.js
const path = require( 'path' );
const fs = require( 'fs' );
const express = require( 'express' );
const axios = require( 'axios' );
const cors = require( 'cors' );
const bodyParser = require( 'body-parser' );
const generateSignature = require( './utils/generateSignature' ); // See: https://ckeditor.com/docs/cs/latest/examples/security/request-signature-nodejs.html.
const editorBundle = fs.readFileSync( path.resolve( '../client/build/ckeditor.js' ) ); // It should be your bundled editor.

const app = express();
const port = 8000; // The default application port.
const apiSecret = 'SECRET'; // Do not forget to hide this value in a safe place e.g. a .env file!
const organizationId = 'organizationId'; // Type your organization ID here.
const environmentId = 'environmentId'; // Type your environment ID here.
// If you use On-Premises application you can adjust baseApiUrl accordingly with your application URL.
const baseApiUrl = `https://${ organizationId }.cke-cs.com/api/v5/${ environmentId }`;

app.use( bodyParser.urlencoded( { extended: true } ) );
app.use( bodyParser.json() );
app.use( cors() );

// This function will be responsible for sending requests to CKEditor Cloud Services API.
async function sendRequest( method, url, body ) {
    const CSTimestamp = Date.now();
    const payload = {
        method,
        url,
        mode: 'no-cors',
        headers: {
            'Content-Type': 'application/json',
            'X-CS-Signature': generateSignature( apiSecret, method, url, CSTimestamp, body ),
            'X-CS-Timestamp': CSTimestamp
        }
    };

    if ( method.toUpperCase() !== 'GET' ) {
        payload.data = body;
    }

    try {
        const { status, data } = await axios( payload );

        return { status, data };
    } catch ( { response } ) {
        const { status, data } = response;

        return { status, data };
    }
}

// Upload the editor bundle. Note that you will need to upload your editor again if you change the bundle.
app.post( '/upload-editor', async ( req, res ) => {
    const { bundleVersion } = req.body;

    const { status, data } = await sendRequest( 'POST', `${ baseApiUrl }/editors`, {
        bundle: editorBundle.toString(),
        config: {
            cloudServices: {
                bundleVersion // This value should be unique per environment.
            }
        }
    } );

    return res.json( { status, data } );
} );

// Synchronously imports the document to the storage
app.post( '/storage', async ( req, res ) => {
    const { documentId, documentContent } = req.body;

    const { status, data } = await sendRequest( 
        'POST', 
        `${ baseApiUrl }/storage`,
        {
            document_id: documentId,
            data: documentContent
        }
    );

    return res.json( { status, data } );
} );

// Deletes a single document from the storage.
app.delete( '/storage/:documentId', async ( req, res ) => {
    const { documentId } = req.params;

    const { status } = await sendRequest( 'DELETE', `${ baseApiUrl }/storage/${ documentId }` );

    return res.json( { status } );
} );

// Gets a single document.
app.get( '/storage/:documentId', async ( req, res ) => {
    const { documentId } = req.params;

    const { status, data } = await sendRequest( 'GET', `${ baseApiUrl }/storage/${ documentId }` );

    return res.json( { status, data } );
} );

// Gets a list of documents.
app.get( '/storage', async ( req, res ) => {
    const { status, data } = await sendRequest( 'GET', `${ baseApiUrl }/storage` );

    return res.json( { status, data } );
} );

app.listen( port, () => console.log( `The application is listening on port ${ port }!` ) );

# Usage

Run:

node index.js

By sending HTTP requests to this application you can now perform actions and communicate with CKEditor Cloud Services. See the following example:

  1. Upload an editor bundle.
try {
    const response = await fetch( 'http://localhost:8000/upload-editor', {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json'
        },
        body: JSON.stringify( { bundleVersion: '1.0.0' } )
    } );

    const data = await response.json();

    console.log( 'Result of uploading editor:', data );
} catch ( error ) {
    console.log( 'Error occurred:', error );
}
  1. Synchronously import the document to the storage
try {
    const response = await fetch( 'http://localhost:8000/storage', {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json'
        },
        body: JSON.stringify( { 
            documentId: "document-1",
            documentContent: "<p>Lorem Ipsum is <b>simply dummy</b> text of the printing and typesetting industry.</p>",
        } )
    } );

    const data = await response.json();

    console.log( 'Result of inserting document:', data );
} catch ( error ) {
    console.log( 'Error occurred:', error );
}
  1. Get a list of documents – an array with the newly created document-1 should be returned under the data property.
try {
    const response = await fetch( `http://localhost:8000/storage`, {
        method: 'GET',
        headers: {
            'Content-Type': 'application/json'
        }
    } );

    const data = await response.json();

    console.log( 'Result of getting documents:', data );
} catch ( error ) {
    console.log( 'Error occurred:', error );
}
  1. Get the document with the document-1 ID. The content returned should be equal to the content imported in step 2.
try {
    const response = await fetch( `http://localhost:8000/storage/document-1`, {
        method: 'GET',
        headers: {
            'Content-Type': 'application/json'
        }
    } );

    const data = await response.json();

    console.log( 'Result of getting document:', data );
} catch ( error ) {
    console.log( 'Error occurred:', error );
}
  1. Delete the document with the document-1 ID.
try {
    const response = await fetch( `http://localhost:8000/storage/document-1`, {
        method: 'DELETE',
        headers: {
            'Content-Type': 'application/json'
        }
    } );

    const data = await response.json();

    console.log( 'Result of deleting document:', data );
} catch ( error ) {
    console.log( 'Error occurred:', error );
}
  1. Try to get the document with the document-1 ID again. An error will be thrown explaining that the document does not exist.
try {
    const response = await fetch( `http://localhost:8000/storage/document-1`, {
        method: 'GET',
        headers: {
            'Content-Type': 'application/json'
        }
    } );

    const data = await response.json();

    console.log( 'Result of getting document:', data );
} catch ( error ) {
    console.log( 'Error occurred:', error );
}