# Document storage feature in Node.js

This article presents an example of an application that uses the [document storage feature](../../guides/collaboration/document-storage.md) 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](../../guides/collaboration/document-storage.md#enabling-the-document-storage-feature).

<a id="dependencies">

## Dependencies

This example uses the following dependencies:

* `axios`
* `body-parser`
* `cors`
* `express`

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

<a id="example">

## Example

The following example allows you to upload an [editor bundle](../editor-bundle/editor-bundle-nodejs.md), 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.

> **Warning**
>
> Remember to provide a correct API secret and generate a proper [request signature](../security/request-signature-nodejs.md).

```javascript
// 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 }!` ) );
```

<a id="usage">

## Usage

Run:

```bash
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.

```js
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 );
}
```

2. Synchronously import the document to the storage

```js
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 );
}
```

3. Get a list of documents – an array with the newly created `document-1` should be returned under the `data` property.

```js
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 );
}
```

4. Get the document with the `document-1` ID. The content returned should be equal to the content imported in step 2.

```js
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 );
}
```

5. Delete the document with the `document-1` ID.

```js
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 );
}
```

6. Try to get the document with the `document-1` ID again. An error will be thrown explaining that the document does not exist.

```js
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 );
}
```

---

Full index of the Cloud Services documentation: [llms.txt](../../../llms.txt)
