Public and private schema with Apollo Server and Express

(you can read a better solution here)

The GraphQL Spec that defines how errors should be handled. That means Apollo Server cannot send 401 server error, each request sent http status 200. Even if you have an authenticate issue, it will return a JSON with http status 200. My plan to handle it is to create two shemas, one private and one public.

Apollo Server query with authenticate user
Apollo Server query with authenticate user

Apollo Server middleware problem#

The type-graphql middleware will return an http status 200 with a JSON with the UNAUTHENTICATED error code.

@Resolver()
class UserResolver {
@Query(() => [User])
@UseMiddleware(isAuth)
users() {
return User.find();
}
}

Create Apollo Server Schema#

This is the private schema. The line 3 has the route for the private queries and mutations. The line 9 has the path name, it means the endpoint will be in locahost:4000/graphql/private. For the public you have to do the same, change the resolvers path and the path. In my case I have /graphql/privatefor the private resolvers and /graphql/public for the public ones.

const server = async (app: Express) => {
const apolloSchema = await buildSchema({
resolvers: [path.join(__dirname, '../../../graphql/private/**/*.ts')],
validate: false,
});
const apolloServer = new ApolloServer({ schema: apolloSchema });
apolloServer.applyMiddleware({
app,
path: '/graphql/private',
});
};

Create Middleware#

This is the middleware to block /graphql/private from unauthenticated users. Unfortunately, the "application-level middleware" in ExpressJS does not show all application information within middleware. But, at least I found a way to see the current request url.

const isAuthMiddleware = async (req: Request, res: Response, next: NextFunction) => {
const url = req.params[0] ?? '';
if (url !== '/graphql/private') {
next();
return;
}
getUserByOauthToken(req).then((user) => {
Auth.setUser(user);
next();
}).catch((error) => {
if (error instanceof AuthenticationError) {
res.status(401).send({ message: error.message });
} else {
res.status(500).send({ message: error.message });
}
});
};

Add the GraphQL server and Middleware to Express#

This is the express app, the private and public Apollo Server was added. And the isAuthMiddleware to allow authenticated users only for the url graphql/private.

const getApp = async (): Promise<Express> => {
const app = express();
app.use('*', isAuthMiddleware);
await privateServer(app);
await publicServer(app);
return app;
};

Finally the solution was to create a private Apollo Server schema, only allow requests for authenticated users. That means that all queries and mutations in that schema will be private. You can see the code in my repo.