# Express

This guide describes the integration of CKBox in an Express application. If you prefer to jump straight to the code, you can find the [complete code of the application](https://github.com/ckbox-io/ckbox-express-example) described in this guide on GitHub.

<a id="prerequisites">

## Prerequisites

Before we start, please ensure that you have a recent LTS version of Node.js installed in your system. If the tool is not available from the command line, please follow the [Node.js](https://nodejs.org/en) download instructions first.

<a id="creating-the-application">

## Creating the application

As a base in our example, we will use a project created with [Express project generator](https://expressjs.com/en/starter/generator.html). To start, let’s create a directory for the app, enter it, and use the following command:

```bash
npx express-generator --view=ejs --git
```

After the project is created, update the list of dependencies and install packages:

```bash
npx ncu -u && npm install
```

Finally, we can remove `routes/users.js` and `public/stylesheets/style.css` files from the template since we won’t be using them.

<a id="environment-variables">

## Environment variables

The app we are building relies on a few variables that would be better kept secret. Therefore, before jumping onto the next steps, let’s create a `.env` file in the project’s root. Over the course of this guide, we will be adding our envrionment variables there.

Let’s install the `dotenv` package that will help us use the `.env` file in the project:

```bash
npm add dotenv
```

<a id="authentication">

## Authentication

In a typical scenario access to CKBox will be restricted to [authenticated](../../configuration/authentication.md) users only. Therefore, we will introduce a simple authentication mechanism with the help of [Passport](https://www.passportjs.org/) to our app. Let’s start by installing the required dependencies. For simplicity, we are omitting CSRF protection measures in the app. Please note this example uses a simplified approach that should not necessarily be used in your production application.

```bash
npm add passport passport-local express-session connect-ensure-login
```

Then, let’s create a `routes/auth.js` router that will take care of user authentication. We will be using 3 test users and each one of them will be assigned a different CKBox role: `user`, `admin`, or `superadmin`. Passport’s method `passport.serializeUser` will make sure that the user’s info, including their role in CKBox, will be made available for other route handlers.

Below you can find the complete code for handlers within the authentication router.

```js
// routes/auth.js

const express = require("express");
const passport = require("passport");
const LocalStrategy = require("passport-local");

const dbUsers = [
	{
		id: "1",
		role: "user",
		email: "user@acme.com",
		password: "testpwd123",
		name: "John",
	},
	{
		id: "2",
		role: "admin",
		email: "admin@acme.com",
		password: "testpwd123",
		name: "Joe",
	},
	{
		id: "3",
		role: "superadmin",
		email: "superadmin@acme.com",
		password: "testpwd123",
		name: "Alice",
	},
];

passport.use(
	new LocalStrategy({ usernameField: "email" }, function (email, password, done) {
		const dbUser = dbUsers.find(function (dbUser) {
			return dbUser.email === email && dbUser.password === password;
		});

		if (!dbUser) {
			return done(null, false);
		}

		return done(null, dbUser);
	})
);

passport.serializeUser(function (user, cb) {
	process.nextTick(function () {
		cb(null, { id: user.id, name: user.name, role: user.role });
	});
});

passport.deserializeUser(function (user, cb) {
	process.nextTick(function () {
		return cb(null, user);
	});
});

const router = express.Router();

router.get("/login", function (req, res, next) {
	res.render("login");
});

router.post(
	"/login/password",
	passport.authenticate("local", {
		successReturnToOrRedirect: "/",
		failureRedirect: "/login",
		failureMessage: true,
	})
);

router.post("/logout", function (req, res, next) {
	req.logout(function (err) {
		if (err) {
			return next(err);
		}

		res.redirect("/");
	});
});

module.exports = router;
```

<a id="token-url">

## Token URL

CKBox, like other CKEditor Cloud Services, uses JWT tokens for authentication and authorization. All these tokens are generated on your application side and signed with a shared secret that you can obtain in the [Customer Portal](https://portal.ckeditor.com). For information on how to create access credentials, please refer to the [Creating access credentials](../../configuration/authentication.md#creating-access-credentials) article in the [Authentication guide](../../configuration/authentication.md).

Now that we have the required access credentials, namely: the environment ID and the access key, let’s create the token endpoint. As a base, we will use the code of the [generic token endpoint in Node.js](../../../../../cs/latest/examples/token-endpoints/nodejs.md).

First, let’s install the [jsonwebtoken](https://www.npmjs.com/package/jsonwebtoken) library for creating JWT tokens:

```bash
npm add jsonwebtoken
```

Let’s wrap the logic of the Node.js token endpoint with the Express route handler and generate a token with the [payload required by CKBox](../../configuration/authentication.md#token-payload). Please note that access to this endpoint is limited to authenticated users only.

Let’s create an Express router that will handle all requests related to CKBox. We will rely on the `req.user` object set by Passport for authenticated users.

```js
// routes/ckbox.js

const express = require("express");
const jwt = require("jsonwebtoken");

const CKBOX_ENVIRONMENT_ID = process.env.CKBOX_ENVIRONMENT_ID;
const CKBOX_ACCESS_KEY = process.env.CKBOX_ACCESS_KEY;

const router = express.Router();

router.get("/api/ckbox", function (req, res, next) {
	const user = req.user;

	if (user && user.role) {
		const payload = {
			aud: CKBOX_ENVIRONMENT_ID,
			sub: user.id,
			auth: {
				ckbox: {
					role: user.role,
				},
			},
		};

		const result = jwt.sign(payload, CKBOX_ACCESS_KEY, {
			algorithm: "HS256",
			expiresIn: "1h",
		});

		res.send(result);
	} else {
		next({ message: "Unauthenticated user", status: 401 });
	}
});

module.exports = router;
```

As you can see on the code listing above, the access credentials required to sign JWT tokens are obtained from the environment variables. Thanks to this, you can conveniently add them to the `.env` file:

```bash
# .env
CKBOX_ENVIRONMENT_ID=REPLACE-WITH-ENVIRONMENT-ID
CKBOX_ACCESS_KEY=REPLACE-WITH-ACCESS-KEY
```

<a id="views">

## Views

Now, let’s build a couple of views. We will be using Tailwind CSS to bring an appealing look to our example app. For simplicity, we will be using the Play CDN to use Tailwind right in the browser without any build steps. Please note that [this approach](https://tailwindcss.com/docs/installation/play-cdn) is not the best choice for production.

<a id="login-view">

### Login view

Let’s start by creating a simple login view under `views/login.ejs`. Users will be providing their email addresses and passwords, and we will be using `/login/password` handler created before to authenticate them.

```html
<!-- views/login.ejs -->

<!doctype html>
<html lang="en">
	<head>
		<meta charset="utf-8" />
		<meta name="viewport" content="width=device-width, initial-scale=1" />
		<script src="https://cdn.tailwindcss.com"></script>
		<title>Login</title>
	</head>
	<body>
		<section class="flex justify-center items-center h-screen">
			<form
				action="/login/password"
				method="post"
				class="bg-blue-500 text-center w-1/2 xl:w-1/3 px-3 py-4 rounded flex flex-col gap-2"
			>
				<section class="flex flex-col gap-1">
					<label for="email" class="text-left text-white">Email</label>
					<input
						id="email"
						name="email"
						type="text"
						required
						autofocus
						class="block w-full mx-auto text-sm py-2 px-3 rounded"
					/>
				</section>
				<section class="flex flex-col gap-1">
					<label for="password" class="text-left text-white">Password</label>
					<input
						id="current-password"
						name="password"
						type="password"
						required
						class="block w-full mx-auto text-sm py-2 px-3 rounded my-3"
					/>
				</section>
				<button
					type="submit"
					class="bg-blue text-white font-bold py-2 px-4 rounded border block mx-auto w-full"
				>
					Login
				</button>
			</form>
		</section>
	</body>
</html>
```

<a id="ckbox-with-ckeditor">

### CKBox with CKEditor

In the `views/ckeditor.ejs` view, we will be using the quickest and easiest way to run CKEditor 5 – served from the CDN. For more advanced integration scenarios, please refer to the [CKEditor 5 documentation](https://ckeditor.com/docs/ckeditor5/latest/installation/index.html).

```html
<!-- views/ckeditor.ejs -->

<!doctype html>
<html lang="en">
	<head>
		<meta charset="utf-8" />
		<meta name="viewport" content="width=device-width, initial-scale=1" />
		<script src="https://cdn.tailwindcss.com"></script>
		<script src="https://cdn.ckbox.io/ckbox/2.13.1/ckbox.js"></script>
		<script src="https://cdn.ckeditor.com/ckeditor5/48.5.2/classic/ckeditor.js"></script>
		<link
			rel="stylesheet"
			href="https://cdn.ckbox.io/ckbox/2.13.1/styles/themes/lark.css"
		/>
		<link rel="stylesheet" href="https://cdn.ckeditor.com/ckeditor5/48.5.2/ckeditor5.css" />
		<title>CKEditor</title>
		<style>
			.ck-editor__editable_inline {
				min-height: 400px;
			}
		</style>
	</head>
	<body>
		<div class="mx-auto h-screen flex flex-col">
			<%- include('nav'); %>
			<div class="px-8 flex-1 bg-accents-0">
				<main class="w-full max-w-4xl mx-auto py-16 h-full flex flex-col">
					<section class="flex flex-col gap-6">
						<h2 class="text-4xl font-semibold tracking-tight">CKEditor</h2>
					</section>
					<hr class="border-t border-accents-2 my-6" />
					<section class="flex flex-col gap-3 h-4/5">
						In this example CKBox is integrated with CKEditor. With CKBox plugin,
						CKEditor will upload files directly to your CKBox environment. Use icon in
						the top-left corner of the editor to open CKBox as a file picker.
						<div id="editor"></div>
					</section>
				</main>
			</div>
		</div>

		<script type="importmap">
			{
				"imports": {
					"ckeditor5": "https://cdn.ckeditor.com/ckeditor5/48.5.2/ckeditor5.js",
					"ckeditor5/": "https://cdn.ckeditor.com/ckeditor5/48.5.2/"
				}
			}
		</script>

		<script type="module">
			import {
				ClassicEditor,
				CKBox,
				Essentials,
				Bold,
				Italic,
				Font,
				Paragraph
			} from 'ckeditor5';

			ClassicEditor
				.create( {
					attachTo: document.querySelector( '#editor' ),
					plugins: [ CKBox, Essentials, Bold, Italic, Font, Paragraph ],
					ckbox: {
						tokenUrl: "<%= tokenUrl %>",
						theme: 'lark'
					},
					toolbar: [
						'ckbox', '|', 'undo', 'redo', '|', 'bold', 'italic', '|',
						'fontSize', 'fontFamily', 'fontColor', 'fontBackgroundColor'
					],
				} )
				.catch( error => {
					console.error( error );
				} );
		</script>
	</body>
</html>
```

As you can notice, the view relies on the `nav.ejs` partial, a reusable navigation bar that will be located in most views in our app. Therefore, let’s add the `views/nav.ejs` file.

```html
<!-- views/nav.ejs -->

<nav
	class="border-b border-gray-200 py-5 relative z-20 bg-background shadow-[0_0_15px_0_rgb(0,0,0,0.1)]"
>
	<div class="flex items-center lg:px-6 px-8 mx-auto max-w-7xl">
		<div class="flex-1 hidden md:flex">
			<a href="/" class="text-blue-600 hover:text-blue-800 visited:text-purple-600">Home</a>
		</div>
		<div class="flex-1 justify-end flex items-center md:flex gap-3 h-8">
			<% if (name) { %>
			<span>Welcome, <%= name %>!</span>
			<form action="/logout" method="post">
				<button
					type="submit"
					class="text-white bg-blue-600 hover:bg-blue-700 focus:ring-4 focus:ring-blue-300 font-medium rounded-lg text-sm px-4 py-1.5"
				>
					Sign out
				</button>
			</form>
			<% } else { %>
			<a
				href="/login"
				class="text-white bg-blue-600 hover:bg-blue-700 focus:ring-4 focus:ring-blue-300 font-medium rounded-lg text-sm px-4 py-1.5"
				>Sign in</a
			>
			<% } %>
			<a
				href="https://github.com/ckbox-io/ckbox-express-example"
				target="_blank"
				rel="noreferrer"
				class="text-blue-600 hover:text-blue-800 visited:text-purple-600"
			>
				GitHub
			</a>
		</div>
	</div>
</nav>
```

Then, let’s declare a route for this view in the `routes/ckbox.js` file.

```js
// routes/ckbox.js

// ...

const ensureLoggedIn = require("connect-ensure-login").ensureLoggedIn;
const PUBLIC_URL = process.env.PUBLIC_URL;

router.get("/ckeditor", ensureLoggedIn(), function (req, res) {
	res.render("ckeditor", {
		name: req.user.name,
		tokenUrl: `${PUBLIC_URL}/api/ckbox`,
	});
});

// ...
```

Finally, let’s take the opportunity to add the `PUBLIC_URL=http://localhost:3000` entry to the `.env` file.

<a id="ckbox-as-file-picker">

### CKBox as file picker

One of the common scenarios is to use CKBox as a file picker, where the user can choose one of the files stored in the file manager. After choosing the file, we want to obtain information about the chosen files, especially their URLs. This can be achieved using the [`assets.onChoose`](../../configuration/configuration-options.md#assetsonchoose) callback passed as the CKBox’s configuration option.

Below is the code snippet for the `views/file-picker.ejs` view.

```html
<!-- views/file-picker.ejs -->

<!doctype html>
<html lang="en">
	<head>
		<meta charset="utf-8" />
		<meta name="viewport" content="width=device-width, initial-scale=1" />
		<script src="https://cdn.tailwindcss.com"></script>
		<script src="https://cdn.ckbox.io/ckbox/2.13.1/ckbox.js"></script>
		<title>File picker</title>
	</head>
	<body>
		<div class="mx-auto h-screen flex flex-col">
			<%- include('nav'); %>
			<div class="px-8 flex-1 bg-accents-0">
				<main class="w-full max-w-4xl mx-auto py-16 h-full flex flex-col">
					<section class="flex flex-col gap-6">
						<h2 class="text-4xl font-semibold tracking-tight">File Picker</h2>
					</section>
					<hr class="border-t border-accents-2 my-6" />
					<section class="flex flex-col gap-3">
						One of the common scenarios is to use CKBox as a file picker, where the user
						can choose one of the files stored in the file manager. After choosing the
						file, we want to obtain information about the chosen files, especially their
						URLs.
						<div>
							<button
								id="choose-assets"
								class="text-white bg-blue-600 hover:bg-blue-700 focus:ring-4 focus:ring-blue-300 font-medium rounded-lg text-sm px-4 py-1.5"
							>
								Choose assets
							</button>
						</div>
						<div id="ckbox"></div>
					</section>
					<section class="flex flex-col gap-3">
						<ul id="assets-list"></ul>
					</section>
				</main>
			</div>
		</div>
		<script>
			const chooseBtn = document.getElementById("choose-assets");
			const assetsList = document.getElementById("assets-list");

			const handleChoose = (assets) => {
				assetsList.innerHTML = "";

				const name = assets.forEach(({ data }) => {
					const item = document.createElement("li");
					const name = document.createElement("span");

					name.textContent = `${data.name}.${data.extension}`;

					if (data.url) {
						const link = document.createElement("a");
						const linkClasses = [
							"text-blue-600",
							"hover:text-blue-800",
							"visited:text-purple-600",
						];

						link.classList.add(...linkClasses);
						link.setAttribute("href", data.url);
						link.setAttribute("target", "_blank");
						link.setAttribute("rel", "noreferrer");

						link.appendChild(name);
						item.appendChild(link);
					} else {
						item.appendChild(name);
					}

					assetsList.appendChild(item);
				});
			};

			chooseBtn.addEventListener("click", () => {
				CKBox.mount(document.getElementById("ckbox"), {
					tokenUrl: "<%= tokenUrl %>",
					dialog: true,
					assets: { onChoose: handleChoose },
				});
			});
		</script>
	</body>
</html>
```

Then, let’s declare a route for this view in the `routes/ckbox.js` file.

```js
// routes/ckbox.js

// ...

router.get("/file-picker", ensureLoggedIn(), function (req, res) {
	res.render("file-picker", {
		name: req.user.name,
		tokenUrl: `${PUBLIC_URL}/api/ckbox`,
	});
});

// ...
```

<a id="ckbox-in-inline-mode">

### CKBox in inline mode

To initialize CKBox in inline mode, you can simply mount it in the desired place of your app. This is the default embedding mode for CKBox, so no additional configuration props besides `tokenUrl` are required. CKBox will occupy as much space as allowed by its parent element.

Below is the code snippet for the `views/inline.ejs` view.

```html
<!-- views/inline.ejs -->

<!doctype html>
<html lang="en">
	<head>
		<meta charset="utf-8" />
		<meta name="viewport" content="width=device-width, initial-scale=1" />
		<script src="https://cdn.tailwindcss.com"></script>
		<script src="https://cdn.ckbox.io/ckbox/2.13.1/ckbox.js"></script>
		<title>Inline</title>
	</head>
	<body>
		<div class="mx-auto h-screen flex flex-col">
			<%- include('nav'); %>
			<div class="px-8 flex-1 bg-accents-0">
				<main class="w-full max-w-4xl mx-auto py-16 h-full flex flex-col">
					<section class="flex flex-col gap-6">
						<h2 class="text-4xl font-semibold tracking-tight">Inline</h2>
					</section>
					<hr class="border-t border-accents-2 my-6" />
					<section class="flex flex-col gap-3 flex-1">
						<p>
							To start CKBox in inline mode, you can instantiate it in an arbitrary
							container. CKBox will respect height and width of the container.
						</p>
						<div id="ckbox" class="h-full"></div>
					</section>
				</main>
			</div>
		</div>
		<script>
			CKBox.mount(document.getElementById("ckbox"), {
				tokenUrl: "<%= tokenUrl %>",
			});
		</script>
	</body>
</html>
```

Then, let’s declare a route for this view in the `routes/ckbox.js` file.

```js
// routes/ckbox.js

// ...

router.get("/inline", ensureLoggedIn(), function (req, res) {
	res.render("inline", {
		name: req.user.name,
		tokenUrl: `${PUBLIC_URL}/api/ckbox`,
	});
});

// ...
```

<a id="home-page">

## Home page

Finally, let’s tweak the home page so that it displays links to all views created above. Therefore, let’s replace the contents of the initial `views/index.ejs` file with the snippet outlined below.

```html
<!-- views/index.ejs -->

<!doctype html>
<html lang="en">
	<head>
		<meta charset="utf-8" />
		<meta name="viewport" content="width=device-width, initial-scale=1" />
		<script src="https://cdn.tailwindcss.com"></script>
		<title>Express</title>
	</head>
	<body>
		<div class="mx-auto h-screen flex flex-col">
			<%- include('nav'); %>
			<div class="px-8 flex-1 bg-accents-0">
				<main class="w-full max-w-4xl mx-auto py-16 h-full flex flex-col">
					<section class="flex flex-col gap-6">
						<h2 class="text-3xl font-semibold tracking-tight">
							CKBox integration with Express
						</h2>
					</section>
					<hr class="border-t border-accents-2 my-6" />
					<section class="flex flex-col gap-3">
						<p>Below you can find example integrations of CKBox.</p>
						<p>
							In a typical scenario access to CKBox will be restricted to
							authenticated users only. Therefore, each sample is restricted to signed
							in users only. Use different credentials to unlock various CKBox roles.
							See available users
							<a
								href="https://github.com/ckbox-io/ckbox-express-example/blob/main/routes/auth.js"
								target="_blank"
								rel="noreferrer"
								class="text-blue-600 hover:text-blue-800"
								>here</a
							>.
						</p>
						<div class="flex-1 hidden md:flex gap-2">
							<ol>
								<li>
									<a class="text-blue-600 hover:text-blue-800" href="/inline"
										>Inline mode</a
									>
								</li>
								<li>
									<a class="text-blue-600 hover:text-blue-800" href="/file-picker"
										>File picker</a
									>
								</li>
								<li>
									<a class="text-blue-600 hover:text-blue-800" href="/ckeditor"
										>CKEditor</a
									>
								</li>
							</ol>
						</div>
					</section>
				</main>
			</div>
		</div>
	</body>
</html>
```

Also, let’s adjust the index router by replacing the contents of the `routes/index.js` file with the following snippet:

```js
const express = require("express");
const router = express.Router();

router.get("/", function (req, res) {
	res.render("index", { name: req.user?.name });
});

module.exports = router;
```

<a id="app">

## App

Finally, let’s combine all routes with the main `app.js` file. Replace the file’s contents with the snippet below.

```js
const createError = require("http-errors");
const express = require("express");
const path = require("path");
const session = require("express-session");
const cookieParser = require("cookie-parser");
const logger = require("morgan");
const passport = require("passport");

require("dotenv").config();

const indexRouter = require("./routes/index");
const ckboxRouter = require("./routes/ckbox");
const authRouter = require("./routes/auth");

const app = express();

app.set("views", path.join(__dirname, "views"));
app.set("view engine", "ejs");

app.use(logger("dev"));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, "public")));

app.use(
	session({
		secret: process.env.SESSION_SECRET,
		resave: false,
		saveUninitialized: true,
	})
);
app.use(passport.authenticate("session"));

app.use("/", indexRouter);
app.use("/", authRouter);
app.use("/", ckboxRouter);

app.use(function (req, res, next) {
	next(createError(404));
});

app.use(function (err, req, res, next) {
	res.locals.message = err.message;
	res.locals.error = req.app.get("env") === "development" ? err : {};
	res.status(err.status || 500);
	res.render("error");
});

module.exports = app;
```

User sessions established with the help of the `express-session` package are kept in memory. Therefore, you must sign in again anytime the Express app is started.

As you can see, we’ve used one more environment variable: `SESSION_SECRET`. Its value is used by the [express-session](https://www.npmjs.com/package/express-session) package to sign the session ID cookie and would best be a random set of characters. Below is the complete content of the `.env` file.

```bash
# .env
CKBOX_ENVIRONMENT_ID=REPLACE-WITH-ENVIRONMENT-ID
CKBOX_ACCESS_KEY=REPLACE-WITH-ACCESS-KEY
PUBLIC_URL=http://localhost:3000
SESSION_SECRET=KdTwwFg0zOd3Bp4INE2UmnEogLJK5uma1wdYABaaODs=
```

<a id="congratulations">

## Congratulations

Congratulations on completing the guide! You can now access the app by running the following command:

```bash
npm start
```

<a id="complete-code">

## Complete code

On GitHub, you can find the [complete code of the application](https://github.com/ckbox-io/ckbox-express-example) described in this guide.

---

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