Skip to content

Commit c35374f

Browse files
committed
changes
1 parent f4ab1d1 commit c35374f

File tree

1 file changed

+14
-343
lines changed

1 file changed

+14
-343
lines changed
Lines changed: 14 additions & 343 deletions
Original file line numberDiff line numberDiff line change
@@ -1,353 +1,24 @@
1-
= Using GraphQL middleware with Neo4j GraphQL
1+
[[securing-an-api]]
2+
:description: This page is a tutorial on how to secure your API created with the Neo4j GraphQL Library.
3+
= Securing your GraphQL API
24

3-
You can wrap your auto-generated Neo4j GraphQL resolver with custom logic that intercepts specific GraphQL operations.
4-
This approach allows you to retain all the benefits of auto-generation while adding the custom behavior you need.
5+
Lorem ipsum.
56

7+
== Prerequisites
68

7-
== GraphQL middleware
9+
Lorem ipsum.
810

9-
This page makes use of https://github.com/dimatill/graphql-middleware[`graphql-middleware`].
10-
GraphQL middleware is a library that provides a way to wrap and extend the behavior of your GraphQL resolvers.
11-
It acts as a layer that allows you to apply reusable logic, such as logging, validation, or authentication, across multiple resolvers in a consistent and modular
12-
way.
11+
== Directives
1312

13+
=== Authorization
1414

15-
=== Logging every request
15+
Validate versus filter.
16+
We want to ensure we don't report back database internals to end users. Validate throws an error, which could be a hint of what exists or not.
1617

17-
Consider this Neo4j GraphQL setup:
18+
== Further reading
1819

19-
[source,typescript]
20-
----
21-
import { ApolloServer } from "@apollo/server";
22-
import { startStandaloneServer } from "@apollo/server/standalone";
23-
import { applyMiddleware } from "graphql-middleware";
24-
import * as neo4j from "neo4j-driver";
25-
import { Neo4jGraphQL } from "@neo4j/graphql";
20+
Explanation on different auth options
2621

27-
const typeDefs = /* GraphQL */ `
28-
type User @node {
29-
id: ID! @id
30-
name: String!
31-
email: String!
32-
posts: [Post!]! @relationship(type: "AUTHORED", direction: OUT)
33-
}
22+
Security best practices
3423

35-
type Post @node {
36-
id: ID!
37-
title: String!
38-
content: String!
39-
author: [User!]! @relationship(type: "AUTHORED", direction: IN)
40-
}
41-
42-
type Query {
43-
me: User @cypher(statement: "MATCH (u:User {id: $userId}) RETURN u", columnName: "u")
44-
}
45-
`;
46-
47-
const driver = neo4j.driver("bolt://localhost:7687", neo4j.auth.basic("neo4j", "password"));
48-
49-
const neoSchema = new Neo4jGraphQL({
50-
typeDefs,
51-
driver,
52-
});
53-
54-
const server = new ApolloServer({
55-
schema: await neoSchema.getSchema(),
56-
});
57-
58-
const { url } = await startStandaloneServer(server, {
59-
listen: { port: 4000 },
60-
});
61-
console.log(`🚀 Server ready at ${url}`);
62-
----
63-
64-
Add logging to every single operation without touching the generated schema:
65-
66-
[source,typescript]
67-
----
68-
import { applyMiddleware } from "graphql-middleware";
69-
70-
/* ...existing code... */
71-
72-
const logMiddleware = async (resolve, root, args, context, info) => {
73-
const start = Date.now();
74-
console.log(`🚀 ${info.fieldName} started`);
75-
76-
try {
77-
const result = await resolve(root, args, context, info);
78-
console.log(`✅ ${info.fieldName} completed in ${Date.now() - start}ms`);
79-
return result;
80-
} catch (error) {
81-
console.log(`💥 ${info.fieldName} failed`);
82-
throw error;
83-
}
84-
};
85-
86-
// Wrap your executable schema
87-
const schemaWithLogging = applyMiddleware(await neoSchema.getSchema(), {
88-
Query: logMiddleware,
89-
Mutation: logMiddleware,
90-
});
91-
92-
const server = new ApolloServer({ schema: schemaWithLogging });
93-
----
94-
95-
*That’s it. Every query and mutation is now logged.* Your auto-generated
96-
resolver is unchanged, but you’ve added custom behavior.
97-
98-
Query the users:
99-
100-
[source,graphql]
101-
----
102-
{
103-
users {
104-
name
105-
}
106-
}
107-
----
108-
109-
You should see in your server:
110-
111-
....
112-
🚀 users started
113-
✅ users completed in 23ms
114-
....
115-
116-
117-
=== Email validation before database writes
118-
119-
You can use middleware to enforce specific business rules before data is written to the database.
120-
For example, you can ensure that email addresses provided during user creation are valid.
121-
By using middleware, you can intercept and validate the input before it reaches the Neo4j GraphQL resolver.
122-
123-
Add a middleware that validates the email input in the `createUsers` operation.
124-
A validation error will be thrown before it reaches the Neo4j GraphQL resolver, and the GraphQL client will receive the error message "Invalid email addresses detected".
125-
126-
[source,typescript]
127-
----
128-
/* ...existing code... */
129-
130-
const validateEmails = async (resolve, root, args, context, info) => {
131-
// Only check createUsers mutations
132-
if (info.fieldName === "createUsers") {
133-
// Note: This is a simplistic and intentionally flawed email validation example, but good for demonstration purposes.
134-
const invalidEmails = args.input.filter((user) => !user.email.includes("@"));
135-
if (invalidEmails.length > 0) {
136-
throw new Error("Invalid email addresses detected");
137-
}
138-
}
139-
140-
return resolve(root, args, context, info);
141-
};
142-
143-
const schema = applyMiddleware(
144-
await neoSchema.getSchema(),
145-
{
146-
Query: logMiddleware,
147-
Mutation: logMiddleware,
148-
},
149-
{
150-
Mutation: validateEmails,
151-
}
152-
);
153-
----
154-
155-
Try to create a user with the email "not-an-email":
156-
157-
[source,graphql]
158-
----
159-
mutation createUsers {
160-
createUsers(input: [{ email: "not-an-email.com", name: "firstname" }]) {
161-
users {
162-
email
163-
}
164-
}
165-
}
166-
----
167-
168-
169-
== Working with Neo4j GraphQL
170-
171-
Most of the above is applicable even outside Neo4j GraphQL, but there is an important concept when writing middleware for Neo4j GraphQL resolvers.
172-
173-
Here's the key difference from how traditional GraphQL resolvers are
174-
usually built:
175-
176-
In *traditional GraphQL*, each field resolver executes independently, potentially causing multiple database calls.
177-
By contrast, in *Neo4j GraphQL* the root field resolver (like `users` or `createUsers`) analyzes the entire query tree and executes one optimized Cypher query.
178-
179-
The N+1 problem is solved in Neo4j GraphQL by analyzing the entire GraphQL operation (via the `info` object) and generating optimized Cypher queries that fetch all requested data in a single database round-trip.
180-
181-
Consider this query:
182-
183-
[source,graphql]
184-
----
185-
{
186-
users {
187-
name
188-
email
189-
posts {
190-
title
191-
content
192-
}
193-
}
194-
}
195-
----
196-
197-
Neo4j GraphQL doesn't execute separate resolvers for `name`, `email`, `posts`, `title`, and `content`.
198-
Instead, the `users` field resolver generates and executes a single Cypher query that returns all the data at once.
199-
The nested field resolvers simply return the already fetched data from memory.
200-
201-
202-
=== Timing matters
203-
204-
Timing matters for middleware - by the time the individual field resolvers execute, the database has already been queried and the data is available in the resolver's result.
205-
206-
Consider the `logMiddleware` from above:
207-
208-
[source,typescript]
209-
----
210-
const logMiddleware = async (resolve, root, args, context, info) => {
211-
const start = Date.now();
212-
console.log(`🚀 ${info.fieldName} started`);
213-
214-
try {
215-
const result = await resolve(root, args, context, info);
216-
console.log(`✅ ${info.fieldName} completed in ${Date.now() - start}ms`);
217-
return result;
218-
} catch (error) {
219-
console.log(`💥 ${info.fieldName} failed: ${error.message}`);
220-
throw error;
221-
}
222-
};
223-
----
224-
225-
Apply the `logMiddleware` to queries and the user's name:
226-
227-
[source,typescript]
228-
----
229-
const schema = applyMiddleware(
230-
schema,
231-
{
232-
Query: logMiddleware, // wraps all the Queries and it's executed before the database round-trip
233-
},
234-
{
235-
User: {
236-
name: logMiddleware, // wraps only the User's name field resolver and it's executed after the database roundtrip
237-
},
238-
}
239-
);
240-
----
241-
242-
Run this query:
243-
244-
[source,graphql]
245-
----
246-
query {
247-
users {
248-
name
249-
}
250-
}
251-
----
252-
253-
You should see:
254-
255-
....
256-
🚀 users started
257-
... Neo4j resolver generates and executes Cypher ...
258-
✅ users completed in 48ms
259-
🚀 name started
260-
✅ name completed in 0ms
261-
....
262-
263-
Note how the name resolution happens after the round-trip to the database.
264-
265-
Note the following difference:
266-
267-
* Query and mutation level middleware runs before and after the Neo4j GraphQL autogenerated resolvers.
268-
* Type and field level middleware runs only after the Neo4j GraphQL autogenerated resolvers.
269-
270-
271-
== Stack multiple middleware
272-
273-
It's possible to apply multiple pieces of middleware for the same field.
274-
For instance, you can apply diverse middleware to the same `users` resolver:
275-
276-
[source,typescript]
277-
----
278-
const schema = applyMiddleware(
279-
schema,
280-
{
281-
Query: {
282-
users: async (resolve, root, args, context, info) => {
283-
console.log("A started");
284-
await resolve(root, args, context, info);
285-
console.log("A completed");
286-
},
287-
},
288-
},
289-
{
290-
Query: {
291-
users: async (resolve, root, args, context, info) => {
292-
console.log("B started");
293-
await resolve(root, args, context, info);
294-
console.log("B completed");
295-
},
296-
},
297-
},
298-
{
299-
Query: {
300-
users: async (resolve, root, args, context, info) => {
301-
console.log("C started");
302-
await resolve(root, args, context, info);
303-
console.log("C completed");
304-
},
305-
},
306-
}
307-
);
308-
----
309-
310-
The order in which middleware is applied is important, as they execute one after the other.
311-
Each middleware wraps the next one, creating a chain of execution from outermost to innermost.
312-
313-
Run this query:
314-
315-
[source,graphql]
316-
----
317-
query {
318-
users {
319-
name
320-
}
321-
}
322-
----
323-
324-
Schematic output:
325-
326-
[source,bash]
327-
----
328-
....
329-
A started
330-
B started
331-
C started
332-
... Neo4j GraphQL user resolver ...
333-
C completed
334-
B completed
335-
A completed
336-
....
337-
----
338-
339-
The user's resolver is wrapped in three layers of middleware.
340-
341-
342-
== Conclusion
343-
344-
GraphQL middleware with Neo4j GraphQL gives you the best of both worlds: the power of auto-generated schemas and the flexibility to inject custom logic exactly where you need it.
345-
346-
When you need custom logic, graphql-middleware lets you keep the rapid development benefits of Neo4j GraphQL while adding the custom behavior you need.
347-
348-
The GraphQL ecosystem evolves rapidly.
349-
https://the-guild.dev/[The Guild] has developed https://envelop.dev/[Envelop] with its own https://www.npmjs.com/package/@envelop/graphql-middleware[graphql-middleware
350-
plugin].
351-
352-
This guide uses `graphql-middleware` because it's server-agnostic and delivers the clearest path to understanding middleware with Neo4j GraphQL.
353-
If you need a more comprehensive plugin ecosystem, we recommend exploring envelop.
24+
link:https://neo4j.com/docs/operations-manual/current/authentication-authorization/manage-privileges/[Role-based access control]

0 commit comments

Comments
 (0)