Nextjs and git
Tags: nextjs, git, javascript, api
Published: 2022-09-18 21:44:08

## How to implement working with git in react web application

This article is an attempt to classify knowledge about using git in a nodejs environment. Particularly, this is going to tell about [isomorphic-git](https://github.com/isomorphic-git/isomorphic-git) library and how to implement it in web applications.

Source code for this example can be found [here](https://github.com/vavilov2212/isomorphic-git-nextjs).

### Prerequisites

- nodejs 16.14.0 - this is the one i worked on, but it certainly should work in any version higher, than that
- next.js - version 9 and higher (we need api routes feature)

### Getting into development

First, let’s create blank nextjs application using create-next-app, just so we can skip all the boilerplate.

```jsx
npx create-next-app@latest
```

Now, let’s edit `/pages/index.js`. Let's remove default nextjs stuff, and instead put our own markup. Checkout [source code](https://github.com/vavilov2212/isomorphic-git-nextjs) for dedicated styles.

Our page will look like this

![isomorphic-git example clone page](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/o8m0f1at150pn1wcmjqz.png)

### Clone from git

The clone function is a fetch request to our own api route that is `/pages/api/clone.js` in which we actually going to request git.

```jsx
const [response, setResponse] = useState([]);
const [repoUrl, setRepoUrl] = useState('');

const simpleClone = async () => {
  const cloneRespone = await fetch(
    'api/clone',
      {
        method: 'POST',
        body: JSON.stringify({ repoUrl })
      }
  )
    .then(res => res.json());
	
    setResponse(cloneRespone);
};
```

Nothing fancy so far. The input changes the repoUrl state variable, which is then put to the request body.

Let’s take a look at our `/api/clone` route.

```jsx
import path from 'path';
import git from 'isomorphic-git';
import http from 'isomorphic-git/http/node';
import fs from 'fs';

export default async function handler(req, res) {
  const { repoUrl } = JSON.parse(req.body);

  // Make sure it's clean every time
  if (fs.existsSync('./test-clone')) {
    try {
      fs.rmSync('./test-clone', { recursive: true });
    } catch(e) {
      console.log('fs.rmSync error', e);
    }
  }

  const dir = path.join(process.cwd(), 'test-clone');

  try {
    await git.clone({
      fs,
      http,
      onMessage: console.log,
      onProgress: console.log,
      dir,
      url: repoUrl,
   })
    .then(console.log)
    .catch(console.log);
  } catch(e) {
    console.log('git.clone error', e);
  }

  // Just for sake of readability
  const recursiveDirStruct = (distPath) => {
    return fs.readdirSync(distPath)
      .filter(function (file) {
        if (file === '.git') return false;
        return fs.statSync(distPath + '/' + file).isDirectory();
      })
      .reduce(function(all, subDir) {
        return [...all, ...fs.readdirSync(distPath + '/' + subDir).map(e => subDir + '/' + e)]
      }, []);
  };

  res.status(200).json(recursiveDirStruct('./test-clone'));
};
```

According to [nextjs documentation](https://nextjs.org/docs/api-routes/introduction), all files in the pages/api folder must export a request handler function instead of a React Component.

It first parses the incoming body parameters and makes shure to delete the folder, to which we will clone, if it was previously created.

Next, we call the clone function. When addressing the isomorphic-git object, it expects you to pass in two main things, on which it relies:

- a way to interact with filesystem
- and a way to make internal network requests

Look into the isomorphic-git [documentation](https://isomorphic-git.org/docs/en/http) to find out, that it contains network modules on it’s own, both for nodejs and client-side use.

As for [filesystem](https://isomorphic-git.org/docs/en/fs), the built-in nodejs fs module will totally do, but you are not restricted to it.

The recursiveDirStruct function just puts all filepaths from the cloned repository into an array, so that it can be conveniently mapped on our frontend.

Finally, let’s test it. Type in the repository address and hit clone. Don’t forget, that the repository must be public for this example, otherwise we’d have to have some authentication. 

I cloned my knowledge base repo and this is what i got

![isomorphic-git example clone result](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sdcmn03s3duoq9l61uk1.png)

### Add and delete files

Allright, we have cloned the repo in nodejs environment. But can we make changes to it? To do this, lets’s add ability to delete and add files.

First lets add new page pages/change.js and copy contents of the index file. Now lets add two things:

- a little trash icon to every line in the list of files, that we fetched from git server.
- A textarea, where we can type in contents of a new file, that we want to create and add to the repository.

I also added some styles, just to make it look better. This is what it looks like


![isomorphic-git example add/delete file page](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9i3ipwow38hx5t6l8ehz.png)

For each of the input and textarea there are dedicated useState variables, that are used to make requests to the server. I’ve also added two request functions. We pass just filepath if we want to delete file, and both filepath and filecontent if we want to add a new file.

```jsx
const [filepath, setAddFilepath] = useState('');
const [filecontent, setFilecontent] = useState('');

const simpleDelete = async (filepath) => {
  await fetch(
    'api/update',
    {
      method: 'POST',
      body: JSON.stringify({ filepath }) 
    }
  )
    .then(response => response.json())
    .catch(e => console.log('error', e));
  };

  const simpleAdd = async () => {
    await fetch(
      'api/update',
      {
        method: 'POST',
        body: JSON.stringify({
          filepath,
          filecontent
        }) 
      }
    )
      .then(response => response.json())
      .catch(e => console.log('error', e));
  };
```

Now let’s take a look at the `api/update`. The concept is the same, so i won’t include the full code here, you can check it out in the [source code](https://github.com/vavilov2212/isomorphic-git-nextjs-js).

```jsx
  ...

  if (fs.existsSync(dir)) {
    try {
      if (filecontent) {
        fs.appendFileSync(file, filecontent);
        await git.add({ fs, dir: './test-clone', filepath  })
      } else {
        await git.remove({ fs, dir: './test-clone', filepath })
      }
    } catch(e) {
      console.log('git.add error', e);
    }
  }

  await git.commit({
    fs,
    dir,
    message,
    author: {
      name: 'Athors name',
      email: 'Authors email',
    },
  })
    .then(console.log)
    .catch(console.log);

  ...
```

First we check the incoming request parameters and if filecontent is present, we assume it’s an addition, otherwise the filepath is removed from repo. If it seems a bit clunky, remember, that this just a simple example, not enterprise software.

Next step, we subsequently call commit and push commands, just like you would do in a terminal. Since we need to be authorized to push to remote git server, isomorphic-git api has a [dedicated onAuth callback](https://isomorphic-git.org/docs/en/onAuth). User credentials may also be required to clone from a private repository.

```jsx
try{
  await git.push({
    fs,
    dir,
    http,
    onProgress: console.log,
    onMessage: console.log,
    remote: 'origin',
    ref: 'master',
    onAuth: () => ({
      username: 'YOUR_USERNAME',
      password: 'YOUR_PASSWORD_OR_PERSONAL_ACCESS_TOKEN'
    }),
  })
    .then(() => res.status(200).json({ success: true }))

  } catch(e) {
    console.log('push error', e);
    res.status(400).json({ error: e.message });
  }
```

For the sake of simplicity, i decided not to make input fields for auth credentials, for the reason, that i’d need to include all the other stuff too (name, email, username, branchname etc.). So just go ahead and change these values in the source code before running it.

If you're using github, you will likely use your personal access token, because github doesn’t support regular passwords anymore.

Now let’s run our program and see what happens. I simply typed in some random path and content and hit Add. After that, clone again to see, that the file was actually added and there you go.


![isomorphic-git example add/delete file result](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vfh6uc04fk3qa367qo5n.png)


Ofcourse, git in front-end applications can do much more, so explore the capabilities of isomorphic-git and thank you for reading.