A powerful and flexible NestJS module for seamless integration with Hasura GraphQL Engine.
✨ Fluent Builder API - Chain methods for clean and readable code
🔐 Built-in Authentication - Admin secret and JWT token support
📁 GraphQL File Loading - Load queries from .gql files with caching
🎯 TypeScript First - Full type safety and IntelliSense support
🚀 Flexible Configuration - Sync and async module registration
🎨 Custom Decorators - Extract auth tokens from request headers
⚡ Performance Optimized - Query caching for improved performance
🧪 Well Tested - Comprehensive unit and integration tests
npm install @mayademcom/nestjs-hasura graphql-requestSynchronous Configuration:
import { Module } from '@nestjs/common';
import { HasuraModule } from '@mayademcom/nestjs-hasura';
@Module({
imports: [
HasuraModule.forRoot({
endpoint: 'https://your-hasura.app/v1/graphql',
adminSecret: 'your-admin-secret', // Optional
}),
],
})
export class AppModule {}Asynchronous Configuration (Recommended for Production):
import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { HasuraModule } from '@mayademcom/nestjs-hasura';
@Module({
imports: [
ConfigModule.forRoot({ isGlobal: true }),
HasuraModule.forRootAsync({
imports: [ConfigModule],
useFactory: (configService: ConfigService) => ({
endpoint: configService.get<string>('HASURA_ENDPOINT'),
adminSecret: configService.get<string>('HASURA_ADMIN_SECRET'),
}),
inject: [ConfigService],
}),
],
})
export class AppModule {}import { Injectable } from '@nestjs/common';
import { HasuraService } from '@mayademcom/nestjs-hasura';
@Injectable()
export class UserService {
constructor(private readonly hasura: HasuraService) {}
async getUsers(limit: number) {
const query = `
query GetUsers($limit: Int!) {
users(limit: $limit) {
id
name
email
}
}
`;
return this.hasura
.requestBuilder()
.withAdminSecret()
.withQuery(query)
.withVariables({ limit })
.execute();
}
}The module provides a fluent builder API for constructing GraphQL requests:
const result = await this.hasura
.requestBuilder()
.withQuery('query { users { id name } }')
.execute();const result = await this.hasura
.requestBuilder()
.withQuery('query GetUser($id: uuid!) { users_by_pk(id: $id) { id name } }')
.withVariables({ id: 'user-id' })
.execute();const result = await this.hasura
.requestBuilder()
.withAdminSecret()
.withQuery('query { users { id } }')
.execute();const result = await this.hasura
.requestBuilder()
.withAuthorizationToken(jwtToken)
.withQuery('query { me { id name } }')
.execute();const result = await this.hasura
.requestBuilder()
.withHeaders({
'x-hasura-role': 'user',
'x-hasura-user-id': '123',
})
.withQuery('query { users { id } }')
.execute();const result = await this.hasura
.requestBuilder()
.withAdminSecret()
.withAuthorizationToken(token)
.withHeaders({ 'x-tenant-id': 'tenant-123' })
.withQuery(query)
.withVariables(variables)
.execute();Store your GraphQL queries in .gql files for better organization:
Create a query file:
# src/queries/get-users.gql
query GetUsers($limit: Int!) {
users(limit: $limit) {
id
name
email
created_at
}
}Use in your service:
@Injectable()
export class UserService {
constructor(private readonly hasura: HasuraService) {}
async getUsers(limit: number) {
return this.hasura
.requestBuilder()
.withAdminSecret()
.withQueryFromFile('src/queries/get-users.gql')
.withVariables({ limit })
.execute();
}
}Benefits:
- ✅ Queries are cached automatically after first load
- ✅ Better syntax highlighting in IDE
- ✅ Easier to maintain and version control
- ✅ Separation of concerns
Extract authorization tokens from request headers:
import { Controller, Post, Body } from '@nestjs/common';
import { Authorization, HasuraService } from '@mayademcom/nestjs-hasura';
@Controller('webhooks')
export class WebhookController {
constructor(private readonly hasura: HasuraService) {}
// Optional token
@Post('public')
async publicWebhook(
@Authorization() token: string | null,
@Body() payload: any,
) {
if (token) {
// Authenticated request
return this.hasura
.requestBuilder()
.withAuthorizationToken(token)
.withQuery('query { me { id } }')
.execute();
}
// Public request
return { message: 'Public endpoint' };
}
// Required token
@Post('protected')
async protectedWebhook(
@Authorization({ required: true }) token: string,
@Body() payload: any,
) {
return this.hasura
.requestBuilder()
.withAuthorizationToken(token)
.withQuery('query { me { id name } }')
.execute();
}
// Custom prefix
@Post('api-key')
async apiKeyWebhook(
@Authorization({ prefix: 'ApiKey' }) apiKey: string | null,
@Body() payload: any,
) {
// Header: "ApiKey xxx-yyy-zzz"
return { apiKey };
}
}Decorator Options:
| Option | Type | Default | Description |
|---|---|---|---|
required |
boolean | false |
Throws UnauthorizedException if token is missing |
prefix |
string | 'Bearer' |
Token prefix to strip from header |
The module provides four registration methods:
Global module with synchronous configuration:
HasuraModule.forRoot({
endpoint: 'https://hasura.app/v1/graphql',
adminSecret: 'secret',
});Global module with asynchronous configuration:
HasuraModule.forRootAsync({
imports: [ConfigModule],
useFactory: (config: ConfigService) => ({
endpoint: config.get('HASURA_ENDPOINT'),
adminSecret: config.get('HASURA_ADMIN_SECRET'),
}),
inject: [ConfigService],
});Module-scoped with synchronous configuration:
HasuraModule.register({
endpoint: 'https://hasura.app/v1/graphql',
});Module-scoped with asynchronous configuration:
HasuraModule.registerAsync({
imports: [ConfigModule],
useFactory: (config: ConfigService) => ({
endpoint: config.get('HASURA_ENDPOINT'),
}),
inject: [ConfigService],
});interface HasuraConfig {
endpoint: string; // Hasura GraphQL endpoint URL
adminSecret?: string; // Optional admin secret for admin operations
}Use module-scoped registration for different Hasura endpoints:
// user.module.ts
@Module({
imports: [
HasuraModule.register({
endpoint: 'https://users-hasura.app/v1/graphql',
adminSecret: 'users-secret',
}),
],
providers: [UserService],
})
export class UserModule {}
// analytics.module.ts
@Module({
imports: [
HasuraModule.register({
endpoint: 'https://analytics-hasura.app/v1/graphql',
adminSecret: 'analytics-secret',
}),
],
providers: [AnalyticsService],
})
export class AnalyticsModule {}The HasuraService extends GraphQLClient from graphql-request, so you can use it directly:
// Using the native GraphQLClient request method
const result = await this.hasura.request(query, variables, headers);Define your GraphQL response types:
interface User {
id: string;
name: string;
email: string;
}
interface GetUsersResponse {
users: User[];
}
const result = await this.hasura
.requestBuilder()
.withQuery(query)
.execute<GetUsersResponse>();
// result.users is now typed as User[]Create a .env file in your project root:
HASURA_ENDPOINT=https://your-hasura.app/v1/graphql
HASURA_ADMIN_SECRET=your-admin-secretCreates a new request builder instance.
Sets the GraphQL query string.
Loads and sets the GraphQL query from a .gql file.
Sets the GraphQL query variables.
Includes the admin secret header from module configuration.
Adds Bearer token authorization header.
Adds custom headers to the request.
Executes the GraphQL request and returns the response.
Extracts and validates authorization token from request headers.
Options:
required?: boolean- Throws error if token is missing (default:false)prefix?: string- Token prefix to remove (default:'Bearer')
Never hardcode credentials:
HasuraModule.forRootAsync({
imports: [ConfigModule],
useFactory: (config: ConfigService) => ({
endpoint: config.get('HASURA_ENDPOINT'),
adminSecret: config.get('HASURA_ADMIN_SECRET'),
}),
inject: [ConfigService],
});Keep your queries in .gql files:
src/
├── queries/
│ ├── users/
│ │ ├── get-users.gql
│ │ ├── create-user.gql
│ │ └── update-user.gql
│ └── posts/
│ ├── get-posts.gql
│ └── create-post.gql
Define response types for better IDE support:
interface GetUsersResponse {
users: Array<{
id: string;
name: string;
email: string;
}>;
}
const result = await hasura
.requestBuilder()
.withQuery(query)
.execute<GetUsersResponse>();try {
const result = await this.hasura
.requestBuilder()
.withAdminSecret()
.withQuery(query)
.execute();
return result;
} catch (error) {
this.logger.error('Hasura query failed', error);
throw new InternalServerErrorException('Failed to fetch data');
}This package uses release-it for automated releases. To create a new release:
- Ensure you're on the
mainbranch - All tests must pass
- Code quality checks must pass
- You have npm publish permissions
# 1. Pull latest changes
git checkout main
git pull origin main
# 2. Run release command (automated versioning)
npm run releaseThe npm run release command will:
- Run the tests
- Analyze commits since last release using Conventional Commits
- Determine version bump automatically (major/minor/patch)
- Update
package.jsonversion - Generate/update
CHANGELOG.md - Create git commit and tag
- Push changes and tags to GitHub
- Publish to npm registry
If you need to manually specify the version:
# Patch release (1.0.0 -> 1.0.1)
npm run release -- patch
# Minor release (1.0.0 -> 1.1.0)
npm run release -- minor
# Major release (1.0.0 -> 2.0.0)
npm run release -- major
# Specific version
npm run release -- 1.2.3
# Pre-release versions
npm run release -- prepatch --preRelease=beta # 1.0.0 -> 1.0.1-beta.0
npm run release -- preminor --preRelease=alpha # 1.0.0 -> 1.1.0-alpha.0Test the release process without actually releasing:
npm run release -- --dry-runThis will show you what would happen without making any changes.
Follow Conventional Commits for automatic versioning.
Authentication Issues:
# Login to npm
npm login
# Verify authentication
npm whoamiGit Issues:
# Ensure working directory is clean
git status
# Ensure you're on main branch
git checkout main
# Pull latest changes
git pull origin mainDry Run First:
# Always test first
npm run release -- --dry-runThis will show you exactly what will happen without making any changes.
Contributions are welcome! Please feel free to submit a Pull Request.
# Clone the repository
git clone https://github.com/mayademcom/nestjs-hasura.git
cd nestjs-hasura
# Install dependencies
npm install
# Run tests
npm test
# Run tests in watch mode
npm run test:watch
# Run tests with coverage
npm run test:cov
# Build the package
npm run build
# Lint code
npm run lint# All tests
npm test
# Unit tests only
npm run test:unit
# Integration tests only
npm run test:integration
# Watch mode
npm run test:watch
# Coverage report
npm run test:covMIT © Mayadem
- 📫 Issues
- 📖 Documentation
See CHANGELOG.md for release history.
Made with ❤️ by Mayadem