-
Notifications
You must be signed in to change notification settings - Fork 1.6k
feat: Add Neptune Analytics example #1262
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
andy-k-improving
wants to merge
8
commits into
vercel:main
Choose a base branch
from
andy-k-improving:ft-hf-na-example
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
6e32add
CRUD
andy-k-improving e8dad45
Update edge endpoint
andy-k-improving 6a2aafb
Update the examples page to fetch a node or edge by id
acarbonetto d8bb619
Update the examples page to show all CRUD operations for both nodes a…
acarbonetto 34568cd
Update POST (create node) to not expect ID, and allow NA to generate …
acarbonetto dba28f7
Fix PUT (update node)
acarbonetto 7cfd6b8
Merge pull request #2 from andy-k-improving/acarbo/na-example-ui-update
andy-k-improving 54c338f
Merge branch 'main' into ft-hf-na-example
acarbonetto File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| AWS_ACCESS_KEY_ID= | ||
| AWS_SECRET_ACCESS_KEY= | ||
| AWS_REGION=us-west-2 | ||
| GRAPH_ID=neptune-analytics-graph-id |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| { | ||
| "root": true, | ||
| "extends": "next/core-web-vitals" | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| # 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/ | ||
| next-env.d.ts | ||
|
|
||
| # Production | ||
| build | ||
| dist | ||
|
|
||
| # Misc | ||
| .DS_Store | ||
| *.pem | ||
|
|
||
| # Debug | ||
| npm-debug.log* | ||
| yarn-debug.log* | ||
| yarn-error.log* | ||
|
|
||
| # Local ENV files | ||
| .env.local | ||
| .env.development.local | ||
| .env.test.local | ||
| .env.production.local | ||
|
|
||
| # Vercel | ||
| .vercel | ||
|
|
||
| # Turborepo | ||
| .turbo | ||
|
|
||
| # typescript | ||
| *.tsbuildinfo |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,130 @@ | ||
| --- | ||
| name: AWS Neptune Analytics with Next.js API Routes | ||
| slug: aws-neptune-analytics-nextjs-api-routes | ||
| description: Learn to use AWS Neptune Analytics with Next.js API Routes for graph database operations. | ||
| framework: Next.js | ||
| deployUrl: https://vercel.com/new/clone?repository-url=https://github.com/vercel/examples/tree/main/solutions/aws-neptune-analytics&project-name=aws-neptune-analytics&repository-name=aws-neptune-analytics&env=GRAPH_ID&envDescription=AWS%20Neptune%20Analytics%20Graph%20ID | ||
| --- | ||
|
|
||
| # Next.js + AWS Neptune Analytics | ||
|
|
||
| This is an example of a Next.js application using AWS Neptune Analytics for creating, reading, updating, and deleting graph nodes and edges with OpenCypher queries. | ||
|
|
||
| ## How to Use | ||
|
|
||
| ### **Option 1: Use an existing Neptune Analytics graph.** | ||
|
|
||
| Retrieve your existing graph ID and ensure proper AWS credentials are configured. Provide the graph ID after clicking "Deploy" to automatically set the environment variable. | ||
|
|
||
| [](https://vercel.com/new/clone?repository-url=https://github.com/vercel/examples/tree/main/solutions/aws-neptune-analytics&project-name=aws-neptune-analytics&repository-name=aws-neptune-analytics&env=GRAPH_ID&envDescription=AWS%20Neptune%20Analytics%20Graph%20ID) | ||
|
|
||
| ### **Option 2: Create a new Neptune Analytics graph.** | ||
|
|
||
| Execute [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app) with [pnpm](https://pnpm.io/installation) to bootstrap the example: | ||
|
|
||
| ```bash | ||
| pnpm create next-app --example https://github.com/vercel/examples/tree/main/solutions/aws-neptune-analytics | ||
| ``` | ||
|
|
||
| 1. Create a new [IAM role](https://aws.amazon.com/iam/) that includes permissions `neptune-graph:ReadDataViaQuery`, `neptune-graph:WriteDataViaQuery` and `neptune-graph:DeleteDataViaQuery` | ||
| 2. Save the access key and secret key or configure AWS credentials (see [AWS CLI configuration guide](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-files.html) for details). | ||
| 3. Create a new Neptune Analytics graph on AWS console | ||
| In the Neptune Analytics Console, create a new graph with public endpoint enabled and 16 NCUs to start. | ||
| 4. Save the graph ID from the Neptune Analytics console | ||
| 5. Create an `.env.local` file and add your graph ID: | ||
| ``` | ||
| GRAPH_ID=your-graph-id-here | ||
| ``` | ||
| Alternatively, you can set it directly in your terminal: | ||
| ``` | ||
| export GRAPH_ID=your-graph-id-here | ||
| ``` | ||
| 6. Run `pnpm dev` to start the Next app at http://localhost:3000 | ||
|
|
||
| Deploy it to the cloud with [Vercel](https://vercel.com/new?utm_source=github&utm_medium=readme&utm_campaign=edge-middleware-eap) ([Documentation](https://nextjs.org/docs/deployment)). | ||
|
|
||
| ## Credentials and Environment Variables | ||
|
|
||
| AWS credentials (e.g. `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY`) and region configuration (e.g. `AWS_REGION`) can be used directly as environment variables for Vercel deployments. | ||
|
|
||
| The AWS SDK will automatically pick up these credentials from the environment: | ||
|
|
||
| ```js | ||
| const client = new NeptuneGraphClient({}) | ||
| ``` | ||
|
|
||
| ## API Endpoints | ||
|
|
||
| The application provides a RESTful API for graph node and edge operations: | ||
|
|
||
| ### Node Operations | ||
|
|
||
| - `GET /api/node?id={id}` - Retrieve a node by ID | ||
| - `POST /api/node` - Create a new node | ||
| - `PUT /api/node` - Update an existing node | ||
| - `DELETE /api/node?id={id}` - Delete a node and its relationships | ||
|
|
||
| ### Edge Operations | ||
|
|
||
| - `GET /api/edge?id={id}` - Retrieve an edge by ID | ||
| - `POST /api/edge` - Create a new edge | ||
| - `PUT /api/edge` - Update an existing edge | ||
| - `DELETE /api/edge?id={id}` - Delete an edge | ||
|
|
||
| ## Testing | ||
|
|
||
| ### Create Node (POST) | ||
|
|
||
| ```bash | ||
| curl -X POST http://localhost:3000/api/node \ | ||
| -d '{"id": "user-123", "name": "John Doe", "type": "user"}' \ | ||
| -H "Content-type: application/json" | ||
| ``` | ||
|
|
||
| ### Get Node (GET) | ||
|
|
||
| ```bash | ||
| curl "http://localhost:3000/api/node?id=user-123" | ||
| ``` | ||
|
|
||
| ### Update Node (PUT) | ||
|
|
||
| ```bash | ||
| curl -X PUT http://localhost:3000/api/node \ | ||
| -d '{"id": "user-123", "name": "John Smith", "type": "user", "active": true}' \ | ||
| -H "Content-type: application/json" | ||
| ``` | ||
|
|
||
| ### Delete Node (DELETE) | ||
|
|
||
| ```bash | ||
| curl -X DELETE "http://localhost:3000/api/node?id=user-123" | ||
| ``` | ||
|
|
||
| ### Create Edge (POST) | ||
|
|
||
| ```bash | ||
| curl -X POST http://localhost:3000/api/edge \ | ||
| -d '{"fromId": "user-123", "toId": "user-456", "type": "FOLLOWS"}' \ | ||
| -H "Content-type: application/json" | ||
| ``` | ||
|
|
||
| ### Get Edge (GET) | ||
|
|
||
| ```bash | ||
| curl "http://localhost:3000/api/edge?id=follows-001" | ||
| ``` | ||
|
|
||
| ### Update Edge (PUT) | ||
|
|
||
| ```bash | ||
| curl -X PUT http://localhost:3000/api/edge \ | ||
| -d '{"id": "follows-001", "since": "2024-01-15", "strength": "strong"}' \ | ||
| -H "Content-type: application/json" | ||
| ``` | ||
|
|
||
| ### Delete Edge (DELETE) | ||
|
|
||
| ```bash | ||
| curl -X DELETE "http://localhost:3000/api/edge?id=follows-001" | ||
| ``` |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,147 @@ | ||
| import { NextResponse } from 'next/server' | ||
| import * as NG from '@aws-sdk/client-neptune-graph' | ||
|
|
||
| const client = new NG.NeptuneGraphClient({}) | ||
| const GRAPH_ID = process.env.GRAPH_ID | ||
|
|
||
| if (!GRAPH_ID) { | ||
| throw new Error('GRAPH_ID environment variable is required') | ||
| } | ||
|
|
||
| /** | ||
| * Execute Neptune Analytics query with parameters | ||
| */ | ||
| async function executeQuery( | ||
| queryString: string, | ||
| parameters: Record<string, any> | ||
| ) { | ||
| const input = { | ||
| graphIdentifier: GRAPH_ID, | ||
| queryString, | ||
| language: NG.QueryLanguage.OPEN_CYPHER, | ||
| parameters, | ||
| } | ||
|
|
||
| const cmd = new NG.ExecuteQueryCommand(input) | ||
| const response = await client.send(cmd) | ||
| const responseStr = await response.payload.transformToString() | ||
| return JSON.parse(responseStr) | ||
| } | ||
|
|
||
| /** | ||
| * Handle errors with consistent logging and response format | ||
| */ | ||
| function handleError(error: any, method: string) { | ||
| console.error(`${method} /api/edge error:`, error) | ||
| return NextResponse.json({ error: 'Internal server error' }, { status: 500 }) | ||
| } | ||
|
|
||
| /** | ||
| * Validate ID parameter from query string | ||
| */ | ||
| function validateId(id: string | null) { | ||
| if (!id) { | ||
| return NextResponse.json( | ||
| { error: 'id parameter is required' }, | ||
| { status: 400 } | ||
| ) | ||
| } | ||
| return null | ||
| } | ||
|
|
||
| /** | ||
| * Validate request body contains required fields for edge creation | ||
| */ | ||
| function validateEdgeBody(body: any) { | ||
| if (!body?.fromId || !body?.toId || !body?.type) { | ||
| return NextResponse.json( | ||
| { error: 'Request body with fromId, toId, and type is required' }, | ||
| { status: 400 } | ||
| ) | ||
| } | ||
| return null | ||
| } | ||
|
|
||
| export async function GET(request: Request) { | ||
| try { | ||
| const { searchParams } = new URL(request.url) | ||
| const id = searchParams.get('id') | ||
|
|
||
| const error = validateId(id) | ||
| if (error) return error | ||
|
|
||
| const result = await executeQuery( | ||
| 'MATCH()-[r]->() WHERE id(r) = $EDGE_ID RETURN r', | ||
| { EDGE_ID: id } | ||
| ) | ||
|
|
||
| return NextResponse.json(result) | ||
| } catch (error) { | ||
| return handleError(error, 'GET') | ||
| } | ||
| } | ||
|
|
||
| export async function POST(request: Request) { | ||
| try { | ||
| const body = await request.json() | ||
|
|
||
| const error = validateEdgeBody(body) | ||
| if (error) return error | ||
|
|
||
| const { fromId, toId, type, ...properties } = body | ||
|
|
||
| const result = await executeQuery( | ||
| 'MATCH (from), (to) WHERE id(from) = $FROM_ID AND id(to) = $TO_ID CREATE (from)-[r:' + | ||
andy-k-improving marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| type + | ||
| ']->(to) SET r += $PROPERTIES RETURN r', | ||
| { FROM_ID: fromId, TO_ID: toId, PROPERTIES: properties } | ||
| ) | ||
|
|
||
| return NextResponse.json(result, { status: 201 }) | ||
| } catch (error) { | ||
| return handleError(error, 'POST') | ||
| } | ||
| } | ||
|
|
||
| export async function PUT(request: Request) { | ||
| try { | ||
| const body = await request.json() | ||
|
|
||
| if (!body?.id) { | ||
| return NextResponse.json( | ||
| { error: 'Request body with id is required' }, | ||
| { status: 400 } | ||
| ) | ||
| } | ||
|
|
||
| const { id, ...properties } = body | ||
|
|
||
| const result = await executeQuery( | ||
| 'MATCH()-[r]->() WHERE id(r) = $EDGE_ID SET r = $PROPERTIES RETURN r', | ||
| { EDGE_ID: id, PROPERTIES: properties } | ||
| ) | ||
|
|
||
| return NextResponse.json(result) | ||
| } catch (error) { | ||
| return handleError(error, 'PUT') | ||
| } | ||
| } | ||
|
|
||
| export async function DELETE(request: Request) { | ||
| try { | ||
| const { searchParams } = new URL(request.url) | ||
| const id = searchParams.get('id') | ||
|
|
||
| const error = validateId(id) | ||
| if (error) return error | ||
|
|
||
| const result = await executeQuery( | ||
| 'MATCH()-[r]->() WHERE id(r) = $EDGE_ID DELETE r', | ||
| { EDGE_ID: id } | ||
| ) | ||
|
|
||
| return NextResponse.json(result) | ||
| } catch (error) { | ||
| return handleError(error, 'DELETE') | ||
| } | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.