From 6072afc83a63587065a9012222ee5a9a69159df2 Mon Sep 17 00:00:00 2001 From: Elliot Scribner Date: Mon, 23 Aug 2021 18:45:25 -0700 Subject: [PATCH] [examples] Added `with-couchbase` example (#27184) This adds a simple `with-couchbase` example app complete with a check for successful connection (in `index.js`). **Note:** To check the connection, this application runs a simple KV GET operation with a key that does not exist and expects a response of `document not found`. Any other response indicates a failure to connect. Thank you! --- examples/with-couchbase/.env.local.example | 5 + examples/with-couchbase/.gitignore | 33 ++++ examples/with-couchbase/README.md | 95 +++++++++++ examples/with-couchbase/package.json | 14 ++ examples/with-couchbase/pages/_app.js | 7 + examples/with-couchbase/pages/index.js | 78 +++++++++ examples/with-couchbase/public/favicon.ico | Bin 0 -> 15086 bytes examples/with-couchbase/public/vercel.svg | 4 + .../with-couchbase/styles/Home.module.css | 148 ++++++++++++++++++ examples/with-couchbase/styles/globals.css | 11 ++ examples/with-couchbase/util/couchbase.js | 65 ++++++++ 11 files changed, 460 insertions(+) create mode 100644 examples/with-couchbase/.env.local.example create mode 100644 examples/with-couchbase/.gitignore create mode 100644 examples/with-couchbase/README.md create mode 100644 examples/with-couchbase/package.json create mode 100644 examples/with-couchbase/pages/_app.js create mode 100644 examples/with-couchbase/pages/index.js create mode 100644 examples/with-couchbase/public/favicon.ico create mode 100644 examples/with-couchbase/public/vercel.svg create mode 100644 examples/with-couchbase/styles/Home.module.css create mode 100644 examples/with-couchbase/styles/globals.css create mode 100644 examples/with-couchbase/util/couchbase.js diff --git a/examples/with-couchbase/.env.local.example b/examples/with-couchbase/.env.local.example new file mode 100644 index 000000000000000..3860cf6cc902ac4 --- /dev/null +++ b/examples/with-couchbase/.env.local.example @@ -0,0 +1,5 @@ +COUCHBASE_USER= +COUCHBASE_PASSWORD= +COUCHBASE_ENDPOINT= +COUCHBASE_BUCKET= +IS_CLOUD_INSTANCE= diff --git a/examples/with-couchbase/.gitignore b/examples/with-couchbase/.gitignore new file mode 100644 index 000000000000000..b9c882b7965724d --- /dev/null +++ b/examples/with-couchbase/.gitignore @@ -0,0 +1,33 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# local env files +.env.local +.env.development.local +.env.test.local +.env.production.local + +# IDE Files +.idea/ diff --git a/examples/with-couchbase/README.md b/examples/with-couchbase/README.md new file mode 100644 index 000000000000000..383a329d8e9109f --- /dev/null +++ b/examples/with-couchbase/README.md @@ -0,0 +1,95 @@ +## Example app using Couchbase + +[Couchbase](https://www.couchbase.com/) is a modern database for enterprise applications. This example will show you how to connect to and use Couchbase in your Next.js app. + +If you want to learn more about Couchbase, visit the following pages: + +- [Couchbase Docs](https://docs.couchbase.com/) +- [Couchbase Developer Portal](https://developer.couchbase.com/) +- [Couchbase Cloud](https://cloud.couchbase.com/sign-up) + +## Preview + +Preview the example live on [StackBlitz](http://stackblitz.com/): + +[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/vercel/next.js/tree/canary/examples/with-couchbase) + +## Deploy your own + +Once you have access to the environment variables you'll need, deploy the example using [Vercel](https://vercel.com?utm_source=github&utm_medium=readme&utm_campaign=next-example): + +[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/git/external?repository-url=https://github.com/vercel/next.js/tree/canary/examples/with-couchbase&project-name=with-couchbase&repository-name=with-couchbase&env=COUCHBASE_USER,COUCHBASE_PASSWORD,COUCHBASE_ENDPOINT,COUCHBASE_BUCKET,IS_CLOUD_INSTANCE&envDescription=Required%20to%20connect%20the%20app%20with%20Couchbase) + +## How to use + +Execute [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app) with [npm](https://docs.npmjs.com/cli/init) or [Yarn](https://yarnpkg.com/lang/en/docs/cli/create/) to bootstrap the example: + +```bash +npx create-next-app --example with-couchbase with-couchbase-app +# or +yarn create next-app --example with-couchbase with-couchbase-app +``` + +Deploy it to the cloud with [Vercel](https://vercel.com/new?utm_source=github&utm_medium=readme&utm_campaign=next-example) ([Documentation](https://nextjs.org/docs/deployment)). + +## Configuration + +### Set up a Couchbase database + +Set up a Couchbase database either locally or with [Couchbase Cloud](https://cloud.couchbase.com/sign-up). + +Local installation can be accomplished through a variety of methods, but [Docker](https://docs.couchbase.com/server/current/install/getting-started-docker.html) is the simplest. + +After Couchbase is installed, set up a cluster by following [this tutorial](https://docs.couchbase.com/server/current/manage/manage-nodes/create-cluster.html). + +- _NOTE:_ the **eventing** and **analytics** services can be unchecked if memory is a constraint (this is often the case with docker and other local installations). + +A variety of sample buckets can be installed to get up and running with a data model quickly. + +### Set up environment variables + +Copy the `env.local.example` file in this directory to `.env.local` (which will be ignored by Git): + +```bash +cp .env.local.example .env.local +``` + +Set each variable on `.env.local`: + +- `COUCHBASE_USERNAME` - The username of an authorized user on your Couchbase instance +- `COUCHBASE_PASSWORD` - The corresponding password for the username specified above +- `COUCHBASE_ENDPOINT` - The endpoint to connect to. Use `localhost` for a local instance of Couchbase, or the Wide Area Network address for a cloud instance. +- `COUCHBASE_BUCKET` - The bucket you'd like to connect to for testing. +- `IS_CLOUD_INSTANCE` - `true` if you are trying to connect to an instance of Couchbase Cloud, `false` otherwise. + +### Run Next.js in development mode + +```bash +npm install +npm run dev +# or +yarn install +yarn dev +``` + +Your app should be up and running on [http://localhost:3000](http://localhost:3000)! If it doesn't work, post on [GitHub discussions](https://github.com/vercel/next.js/discussions). + +You will either see a message stating "You are connected to Couchbase" or "You are NOT connected to Couchbase". Ensure that you have provided the correct environment variables. + +When you are successfully connected, you can refer to the [Couchbase Node.js SDK docs](https://docs.couchbase.com/nodejs-sdk/current/hello-world/start-using-sdk.html) for further instructions on how to query your database. + +## Deploy on Vercel + +You can deploy this app to the cloud with [Vercel](https://vercel.com?utm_source=github&utm_medium=readme&utm_campaign=next-example) ([Documentation](https://nextjs.org/docs/deployment)). + +#### Deploy Your Local Project + +To deploy your local project to Vercel, push it to GitHub/GitLab/Bitbucket and [import to Vercel](https://vercel.com/new?utm_source=github&utm_medium=readme&utm_campaign=next-example). + +## Notes + +- When you import your project on Vercel, make sure to click on **Environment Variables** and set the keys to match your `.env.local` file. + +- For a cloud deployment on Vercel, the **Environment Variables** values will need to **correspond to a cloud instance of Couchbase** (localhost will **NOT** connect from a remote server such as Vercel). Find info on [getting started with Couchbase cloud](https://developer.couchbase.com/tutorial-cloud-getting-started/). + + - _Important:_ you will have to allowlist 0.0.0.0/0 as the IP address, since Vercel's serverless deployments use [dynamic IP addresses](https://vercel.com/docs/solutions/databases#allowing-&-blocking-ip-addresses) diff --git a/examples/with-couchbase/package.json b/examples/with-couchbase/package.json new file mode 100644 index 000000000000000..d5d061dc33aac95 --- /dev/null +++ b/examples/with-couchbase/package.json @@ -0,0 +1,14 @@ +{ + "private": true, + "scripts": { + "dev": "next dev", + "build": "next build", + "start": "next start" + }, + "dependencies": { + "couchbase": "^3.1.3", + "next": "latest", + "react": "^17.0.2", + "react-dom": "^17.0.2" + } +} diff --git a/examples/with-couchbase/pages/_app.js b/examples/with-couchbase/pages/_app.js new file mode 100644 index 000000000000000..1e1cec92425c8b3 --- /dev/null +++ b/examples/with-couchbase/pages/_app.js @@ -0,0 +1,7 @@ +import '../styles/globals.css' + +function MyApp({ Component, pageProps }) { + return +} + +export default MyApp diff --git a/examples/with-couchbase/pages/index.js b/examples/with-couchbase/pages/index.js new file mode 100644 index 000000000000000..a50b899f7408e78 --- /dev/null +++ b/examples/with-couchbase/pages/index.js @@ -0,0 +1,78 @@ +import Head from 'next/head' +import styles from '../styles/Home.module.css' +import { connectToDatabase } from '../util/couchbase' + +export default function Home({ isConnected }) { + return ( +
+ + Create Next App + + + +
+

+ Welcome to Next.js with Couchbase! +

+ + {isConnected ? ( +

+ You are connected to Couchbase +

+ ) : ( + <> +

+ You are NOT connected to Couchbase. Try refreshing the page, and + if this error persists check the README.md for + instructions. +

+ + Note: if the database was recently started, you might have to + re-start the app (in dev mode) or re-deploy to your serverless + environment for changes to take effect. + + + )} + +

+ Get started by editing{' '} + pages/index.js +

+
+ + +
+ ) +} + +export async function getServerSideProps(context) { + let connection = await connectToDatabase() + + const { collection } = connection + + // Check connection with a KV GET operation for a key that doesnt exist + let isConnected = false + try { + await collection.get('testingConnectionKey') + } catch (err) { + // error message will return 'document not found' if and only if we are connected + // (but this document is not present, we're only trying to test the connection here) + if (err.message === 'document not found') { + isConnected = true + } + // if the error message is anything OTHER THAN 'document not found', the connection is broken + } + + return { + props: { isConnected }, + } +} diff --git a/examples/with-couchbase/public/favicon.ico b/examples/with-couchbase/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..4965832f2c9b0605eaa189b7c7fb11124d24e48a GIT binary patch literal 15086 zcmeHOOH5Q(7(R0cc?bh2AT>N@1PWL!LLfZKyG5c!MTHoP7_p!sBz0k$?pjS;^lmgJ zU6^i~bWuZYHL)9$wuvEKm~qo~(5=Lvx5&Hv;?X#m}i|`yaGY4gX+&b>tew;gcnRQA1kp zBbm04SRuuE{Hn+&1wk%&g;?wja_Is#1gKoFlI7f`Gt}X*-nsMO30b_J@)EFNhzd1QM zdH&qFb9PVqQOx@clvc#KAu}^GrN`q5oP(8>m4UOcp`k&xwzkTio*p?kI4BPtIwX%B zJN69cGsm=x90<;Wmh-bs>43F}ro$}Of@8)4KHndLiR$nW?*{Rl72JPUqRr3ta6e#A z%DTEbi9N}+xPtd1juj8;(CJt3r9NOgb>KTuK|z7!JB_KsFW3(pBN4oh&M&}Nb$Ee2 z$-arA6a)CdsPj`M#1DS>fqj#KF%0q?w50GN4YbmMZIoF{e1yTR=4ablqXHBB2!`wM z1M1ke9+<);|AI;f=2^F1;G6Wfpql?1d5D4rMr?#f(=hkoH)U`6Gb)#xDLjoKjp)1;Js@2Iy5yk zMXUqj+gyk1i0yLjWS|3sM2-1ECc;MAz<4t0P53%7se$$+5Ex`L5TQO_MMXXi04UDIU+3*7Ez&X|mj9cFYBXqM{M;mw_ zpw>azP*qjMyNSD4hh)XZt$gqf8f?eRSFX8VQ4Y+H3jAtvyTrXr`qHAD6`m;aYmH2zOhJC~_*AuT} zvUxC38|JYN94i(05R)dVKgUQF$}#cxV7xZ4FULqFCNX*Forhgp*yr6;DsIk=ub0Hv zpk2L{9Q&|uI^b<6@i(Y+iSxeO_n**4nRLc`P!3ld5jL=nZRw6;DEJ*1z6Pvg+eW|$lnnjO zjd|8>6l{i~UxI244CGn2kK@cJ|#ecwgSyt&HKA2)z zrOO{op^o*- + + \ No newline at end of file diff --git a/examples/with-couchbase/styles/Home.module.css b/examples/with-couchbase/styles/Home.module.css new file mode 100644 index 000000000000000..ef4de859109aa67 --- /dev/null +++ b/examples/with-couchbase/styles/Home.module.css @@ -0,0 +1,148 @@ +.small { + font-size: 10px; +} + +.center { + text-align: center; +} + +.red, +.error { + color: indianred; +} + +.green, +.success { + color: lightseagreen; +} + +.container { + min-height: 100vh; + padding: 0 0.5rem; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; +} + +.main { + padding: 5rem 0; + flex: 1; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; +} + +.footer { + width: 100%; + height: 100px; + border-top: 1px solid #eaeaea; + display: flex; + justify-content: center; + align-items: center; +} + +.footer img { + margin-left: 0.5rem; +} + +.footer a { + display: flex; + justify-content: center; + align-items: center; + text-decoration: none; + color: inherit; +} + +.title a { + color: #0070f3; + text-decoration: none; +} + +.title a:hover, +.title a:focus, +.title a:active { + text-decoration: underline; +} + +.title { + margin: 0; + line-height: 1.15; + font-size: 4rem; +} + +.title, +.description { + text-align: center; +} + +.subtitle { + font-size: 2rem; + text-align: center; +} + +.description { + line-height: 1.5; + font-size: 1.5rem; +} + +.code { + background: #fafafa; + border-radius: 5px; + padding: 0.75rem; + font-size: 1.1rem; + font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono, + Bitstream Vera Sans Mono, Courier New, monospace; +} + +.grid { + display: flex; + align-items: center; + justify-content: center; + flex-wrap: wrap; + + max-width: 800px; + margin-top: 3rem; +} + +.card { + margin: 1rem; + flex-basis: 45%; + padding: 1.5rem; + text-align: left; + color: inherit; + text-decoration: none; + border: 1px solid #eaeaea; + border-radius: 10px; + transition: color 0.15s ease, border-color 0.15s ease; +} + +.card:hover, +.card:focus, +.card:active { + color: #0070f3; + border-color: #0070f3; +} + +.card h3 { + margin: 0 0 1rem 0; + font-size: 1.5rem; +} + +.card p { + margin: 0; + font-size: 1.25rem; + line-height: 1.5; +} + +.logo { + height: 1em; +} + +@media (max-width: 600px) { + .grid { + width: 100%; + flex-direction: column; + } +} diff --git a/examples/with-couchbase/styles/globals.css b/examples/with-couchbase/styles/globals.css new file mode 100644 index 000000000000000..8147d4355dbd064 --- /dev/null +++ b/examples/with-couchbase/styles/globals.css @@ -0,0 +1,11 @@ +html, +body { + padding: 0; + margin: 0; + font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, + Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; +} + +* { + box-sizing: border-box; +} diff --git a/examples/with-couchbase/util/couchbase.js b/examples/with-couchbase/util/couchbase.js new file mode 100644 index 000000000000000..968bb9c461d6407 --- /dev/null +++ b/examples/with-couchbase/util/couchbase.js @@ -0,0 +1,65 @@ +import couchbase from 'couchbase' + +const COUCHBASE_USER = process.env.COUCHBASE_USER +const COUCHBASE_PASSWORD = process.env.COUCHBASE_PASSWORD +const COUCHBASE_ENDPOINT = process.env.COUCHBASE_ENDPOINT || 'localhost' +const COUCHBASE_BUCKET = process.env.COUCHBASE_BUCKET || 'travel-sample' +let IS_CLOUD_INSTANCE = process.env.IS_CLOUD_INSTANCE || 'false' + +if (!COUCHBASE_USER) { + throw new Error( + 'Please define the COUCHBASE_USER environment variable inside .env.local' + ) +} + +if (!COUCHBASE_PASSWORD) { + throw new Error( + 'Please define the COUCHBASE_PASSWORD environment variable inside .env.local' + ) +} + +/** + * Global is used here to maintain a cached connection across hot reloads + * in development. This prevents connections growing exponentially + * during API Route usage. + */ +let cached = global.couchbase + +if (!cached) { + cached = global.couchbase = { conn: null } +} + +async function createCouchbaseCluster() { + if (cached.conn) { + return cached.conn + } + + cached.conn = await couchbase.connect( + 'couchbase://' + + COUCHBASE_ENDPOINT + + (IS_CLOUD_INSTANCE === 'true' + ? '?ssl=no_verify&console_log_level=5' + : ''), + { + username: COUCHBASE_USER, + password: COUCHBASE_PASSWORD, + } + ) + + return cached.conn +} + +export async function connectToDatabase() { + const cluster = await createCouchbaseCluster() + + const bucket = cluster.bucket(COUCHBASE_BUCKET) + const collection = bucket.defaultCollection() + + let dbConnection = { + cluster, + bucket, + collection, + } + + return dbConnection +}