Integrating with the GitHub API using Node.js, TypeScript, and NestJS involves several steps. Here's a detailed guide to achieve this:
1. Set Up a New NestJS Project
First, create a new NestJS project if you don't have one already:
npm i -g @nestjs/cli
nest new github-pr-automation
cd github-pr-automation
2. Install Axios and Other Necessary Dependencies
You need Axios to make HTTP requests:
npm install axios
npm install @nestjs/config
npm install @types/axios --save-dev
3. Configure Environment Variables
Create a .env
file in the root of your project and add your GitHub personal access token and other necessary configurations:
GITHUB_TOKEN=your_personal_access_token
GITHUB_OWNER=your_github_username_or_org
GITHUB_REPO=your_repository_name
4. Create a Configuration Module
Set up a configuration module to read environment variables. Update app.module.ts
to import the configuration module:
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { GithubModule } from './github/github.module';
@Module({
imports: [
ConfigModule.forRoot({
isGlobal: true,
}),
GithubModule,
],
})
export class AppModule {}
5. Create the GitHub Module
Generate a GitHub module and service:
nest generate module github
nest generate service github
6. Implement the GitHub Service
In src/github/github.service.ts
, implement the service to create a pull request:
import { Injectable, HttpService } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import axios from 'axios';
@Injectable()
export class GithubService {
private readonly githubToken: string;
private readonly owner: string;
private readonly repo: string;
constructor(private configService: ConfigService) {
this.githubToken = this.configService.get<string>('GITHUB_TOKEN');
this.owner = this.configService.get<string>('GITHUB_OWNER');
this.repo = this.configService.get<string>('GITHUB_REPO');
}
async createPullRequest(head: string, base: string, title: string, body: string): Promise<void> {
const url = `https://api.github.com/repos/${this.owner}/${this.repo}/pulls`;
const data = {
title,
head,
base,
body,
};
const headers = {
Authorization: `token ${this.githubToken}`,
Accept: 'application/vnd.github.v3+json',
};
try {
const response = await axios.post(url, data, { headers });
console.log('Pull request created successfully:', response.data.html_url);
} catch (error) {
console.error('Error creating pull request:', error.response ? error.response.data : error.message);
}
}
}
7. Create a Controller to Trigger the Pull Request
Generate a GitHub controller:
nest generate controller github
In src/github/github.controller.ts
, implement the controller:
import { Controller, Post, Body } from '@nestjs/common';
import { GithubService } from './github.service';
@Controller('github')
export class GithubController {
constructor(private readonly githubService: GithubService) {}
@Post('create-pull-request')
async createPullRequest(
@Body('head') head: string,
@Body('base') base: string,
@Body('title') title: string,
@Body('body') body: string,
): Promise<void> {
await this.githubService.createPullRequest(head, base, title, body);
}
}
8. Testing the API
You can test the API using tools like Postman or curl. Start your NestJS application:
npm run start
Send a POST request to http://localhost:3000/github/create-pull-request
with the required data:
{
"head": "feature-branch",
"base": "main",
"title": "Automated Pull Request",
"body": "This is an automated pull request."
}
9. Full Code Structure
Here’s a summary of the code structure:
src/
├── app.module.ts
├── github/
│ ├── github.controller.ts
│ ├── github.module.ts
│ ├── github.service.ts
├── main.ts
.env
app.module.ts
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { GithubModule } from './github/github.module';
@Module({
imports: [
ConfigModule.forRoot({
isGlobal: true,
}),
GithubModule,
],
})
export class AppModule {}
github.module.ts
import { Module } from '@nestjs/common';
import { GithubService } from './github.service';
import { GithubController } from './github.controller';
@Module({
providers: [GithubService],
controllers: [GithubController],
})
export class GithubModule {}
github.service.ts
import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import axios from 'axios';
@Injectable()
export class GithubService {
private readonly githubToken: string;
private readonly owner: string;
private readonly repo: string;
constructor(private configService: ConfigService) {
this.githubToken = this.configService.get<string>('GITHUB_TOKEN');
this.owner = this.configService.get<string>('GITHUB_OWNER');
this.repo = this.configService.get<string>('GITHUB_REPO');
}
async createPullRequest(head: string, base: string, title: string, body: string): Promise<void> {
const url = `https://api.github.com/repos/${this.owner}/${this.repo}/pulls`;
const data = {
title,
head,
base,
body,
};
const headers = {
Authorization: `token ${this.githubToken}`,
Accept: 'application/vnd.github.v3+json',
};
try {
const response = await axios.post(url, data, { headers });
console.log('Pull request created successfully:', response.data.html_url);
} catch (error) {
console.error('Error creating pull request:', error.response ? error.response.data : error.message);
}
}
}
github.controller.ts
import { Controller, Post, Body } from '@nestjs/common';
import { GithubService } from './github.service';
@Controller('github')
export class GithubController {
constructor(private readonly githubService: GithubService) {}
@Post('create-pull-request')
async createPullRequest(
@Body('head') head: string,
@Body('base') base: string,
@Body('title') title: string,
@Body('body') body: string,
): Promise<void> {
await this.githubService.createPullRequest(head, base, title, body);
}
}
With this setup, you can automate pull requests using the GitHub API within a NestJS application written in TypeScript.
Further, here's how you can extend the existing NestJS service to include the following functions in order to make this more comprehensive.
- Create Pull Requests
- Approve Pull Requests
- Close Pull Requests
- Comment on Pull Requests
- Request Changes on Pull Requests
1. Extending the GitHub Service
Update the GithubService
to include methods for each of these actions:
import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import axios from 'axios';
@Injectable()
export class GithubService {
private readonly githubToken: string;
private readonly owner: string;
private readonly repo: string;
constructor(private configService: ConfigService) {
this.githubToken = this.configService.get<string>('GITHUB_TOKEN');
this.owner = this.configService.get<string>('GITHUB_OWNER');
this.repo = this.configService.get<string>('GITHUB_REPO');
}
private getHeaders() {
return {
Authorization: `token ${this.githubToken}`,
Accept: 'application/vnd.github.v3+json',
};
}
async createPullRequest(head: string, base: string, title: string, body: string): Promise<void> {
const url = `https://api.github.com/repos/${this.owner}/${this.repo}/pulls`;
const data = {
title,
head,
base,
body,
};
try {
const response = await axios.post(url, data, { headers: this.getHeaders() });
console.log('Pull request created successfully:', response.data.html_url);
} catch (error) {
console.error('Error creating pull request:', error.response ? error.response.data : error.message);
}
}
async approvePullRequest(pullRequestNumber: number): Promise<void> {
const url = `https://api.github.com/repos/${this.owner}/${this.repo}/pulls/${pullRequestNumber}/reviews`;
const data = {
event: 'APPROVE',
};
try {
const response = await axios.post(url, data, { headers: this.getHeaders() });
console.log('Pull request approved successfully:', response.data);
} catch (error) {
console.error('Error approving pull request:', error.response ? error.response.data : error.message);
}
}
async closePullRequest(pullRequestNumber: number): Promise<void> {
const url = `https://api.github.com/repos/${this.owner}/${this.repo}/pulls/${pullRequestNumber}`;
const data = {
state: 'closed',
};
try {
const response = await axios.patch(url, data, { headers: this.getHeaders() });
console.log('Pull request closed successfully:', response.data);
} catch (error) {
console.error('Error closing pull request:', error.response ? error.response.data : error.message);
}
}
async commentOnPullRequest(pullRequestNumber: number, comment: string): Promise<void> {
const url = `https://api.github.com/repos/${this.owner}/${this.repo}/issues/${pullRequestNumber}/comments`;
const data = {
body: comment,
};
try {
const response = await axios.post(url, data, { headers: this.getHeaders() });
console.log('Comment added successfully:', response.data);
} catch (error) {
console.error('Error adding comment:', error.response ? error.response.data : error.message);
}
}
async requestChangesOnPullRequest(pullRequestNumber: number, comment: string): Promise<void> {
const url = `https://api.github.com/repos/${this.owner}/${this.repo}/pulls/${pullRequestNumber}/reviews`;
const data = {
body: comment,
event: 'REQUEST_CHANGES',
};
try {
const response = await axios.post(url, data, { headers: this.getHeaders() });
console.log('Requested changes successfully:', response.data);
} catch (error) {
console.error('Error requesting changes:', error.response ? error.response.data : error.message);
}
}
}
2. Update the GitHub Controller
Extend the controller to handle requests for these new methods:
import { Controller, Post, Body, Param, Patch } from '@nestjs/common';
import { GithubService } from './github.service';
@Controller('github')
export class GithubController {
constructor(private readonly githubService: GithubService) {}
@Post('create-pull-request')
async createPullRequest(
@Body('head') head: string,
@Body('base') base: string,
@Body('title') title: string,
@Body('body') body: string,
): Promise<void> {
await this.githubService.createPullRequest(head, base, title, body);
}
@Post('approve-pull-request/:number')
async approvePullRequest(@Param('number') pullRequestNumber: number): Promise<void> {
await this.githubService.approvePullRequest(pullRequestNumber);
}
@Patch('close-pull-request/:number')
async closePullRequest(@Param('number') pullRequestNumber: number): Promise<void> {
await this.githubService.closePullRequest(pullRequestNumber);
}
@Post('comment-pull-request/:number')
async commentOnPullRequest(
@Param('number') pullRequestNumber: number,
@Body('comment') comment: string,
): Promise<void> {
await this.githubService.commentOnPullRequest(pullRequestNumber, comment);
}
@Post('request-changes/:number')
async requestChangesOnPullRequest(
@Param('number') pullRequestNumber: number,
@Body('comment') comment: string,
): Promise<void> {
await this.githubService.requestChangesOnPullRequest(pullRequestNumber, comment);
}
}
3. Testing the API
You can now test the new endpoints using tools like Postman or curl.
- Create Pull Request
POST http://localhost:3000/github/create-pull-request
{
"head": "feature-branch",
"base": "main",
"title": "Automated Pull Request",
"body": "This is an automated pull request."
}
- Approve Pull Request
POST http://localhost:3000/github/approve-pull-request/1
- Close Pull Request
PATCH http://localhost:3000/github/close-pull-request/1
- Comment on Pull Request
POST http://localhost:3000/github/comment-pull-request/1
{
"comment": "This is a comment on the pull request."
}
- Request Changes on Pull Request
POST http://localhost:3000/github/request-changes/1
{
"comment": "Please make the following changes..."
}
With this setup, you can create, approve, close, comment on, and request changes on pull requests using the GitHub API within your NestJS application. What we are missing now are Jest / Vite tests in order to properly build out this system.
Top comments (0)