How to use the GitHub rest API with SvelteKit
Tags: svelte, github, javascript, webdev
Published: 2022-05-02 15:12:01

## Introduction
As a self taught developer wanting to find himself a job in the near future, I was thinking about projects to develop to add to my portfolio. I soon decided upon a markdown editor with a live preview and predefined markdown components, that could be used to write READMEs for GitHub repos (the project can be found [here](https://read-me-murex.vercel.app/). Wanting to continue my practice with SvelteKit, I decided that would be my framework of choice for this project.

After developing a minimum viable product, I thought that pulling existing READMEs from a users GitHub repos would be an awesome feature. As I'm sure many of you know, GitHub is owned by Microsoft. This is apparent when you go to take one look at the documentation for the GitHub rest API, as in true Microsoft fashion, it's pretty poor. This coupled with my use of SvelteKit (which is still in beta) meant that implementing the GitHub rest API was going to be a lot harder than I first thought.

After a lot of research, trial and error I was able to find a YouTube video that focused on what I wanted to do. The only issue was that the video was created with an older version of SvelteKit.
The original video can be found here:

{% embed https://youtu.be/D4ZcbudB1n0 %}

After refactoring for the most recent version of SvelteKit, I decided that I would share what I have learnt, in case anyone in the future faces the same difficulties as me.

## Getting Started
To get started, you need to create a local SvelteKit project as you would normally with the following commands:
```
npm init svelte my-app
cd my-app
npm install
npm run dev
```
Next, it's time to set up things on the GitHub side...

## Set up the OAuth app in GitHub
To set up an OAuth app with GitHub, go to **Your Profile > Settings > Developer Settings > OAuth Apps > New OAuth App** or alternatively, go to this [link](https://github.com/settings/applications/new) if you're already logged into GitHub.

Enter the following into the OAuth setup screen:

- **Application Name:** Whatever you would like
- **Homepage URL:** Set to `http://localhost:3000` for now, as this is the default port that SvelteKit runs on out of the box. If you have configured the port from the default, enter that port instead.
- **Application Description:** Again, whatever you would like
- **Authorization callback URL:** The same as the Homepage URL, with `/callback` at the end. In this example, `http://localhost:3000/callback`

**NOTE: UNLESS CONFIGURED OTHERWISE, ENSURE `http` AND NOT `https` IS USED**

## Environment Variables
Next on the list is to configure the Client ID and Client Secret within your application. To do this, create a file called `.env` in the project root.

In this file, write two variables; `VITE_CLIENT_ID` and `VITE_CLIENT_SECRET`, and then copy these values from the OAuth app settings. You may need to generate a new client secret.
![The client id and secret options in github](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/d93lyhirdgnwqqt39xts.png)
Your `.env` file should look something like this, but with your apps' details:
```
VITE_CLIENT_ID=****cc378
VITE_CLIENT_SECRET=****984b
```
The reason we use the `VITE` prefix is because SvelteKit uses a tool aclled vite under the hood. The `VITE` prefix indicates to vite that the variable should be broadcast to the rest of the application.

## Creating the routes
### Homepage link
Within your index.svelte file, create an a tag that links to "/login" like so:
`<a href="/login">Login</a>`

### Login logic
Then, create a file called `login.js` in the `routes` directory and copy the below code in.
```
// Address to make a request to
const target = "https://github.com/login/oauth/authorize";
// Import from environment variables
const clientId = import.meta.env.VITE_CLIENT_ID;

// Send a request to the target URL with some parameters
export async function get(request) {
	const sessionId = "1234";
	return {
		// 302 is a redirect status code
		status: 302,
		headers: {
			location: `${target}?client_id=${clientId}&state=${sessionId}`,
		},
	};
}
```
This logic makes a GET request to the authorization URL for GitHub, which in turn redirects us to out callback URL (which we entered as `http://localhost:3000/callback`) and gives us a code as a query parameter.

### Callback
Next, create a file called `callback.js` within the same `routes` directory. We want to set the URL to retrieve an access token, the URL to retrieve the User data and import the client id and secret from our `.env` file. Your `callback.js` should look like this so far:
```
const tokenURL = "https://github.com/login/oauth/access_token";
const userURL = "https://api.github.com/user";
const clientId = import.meta.env.VITE_CLIENT_ID;
const secret = import.meta.env.VITE_CLIENT_SECRET;
```

Next, we need to create a function that retrieves the access token and returns it through a POST request.
```
async function getToken(code) {
	const res = await fetch(tokenURL, {
		method: "POST",
		headers: {
			"Content-Type": "application/json",
			Accept: "application/json",
		},
		body: JSON.stringify({
			client_id: clientId,
			client_secret: secret,
			code,
		}),
	});
	const data = await res.json();
	return data.access_token;
}
```
Then, using this token, we can access the user's data using a GET request to the GitHub API.

```
async function getUser(token) {
	const res = await fetch(userURL, {
		headers: {
			Accept: "application/json",
			Authorization: `Bearer ${token}`,
		},
	});
	const data = await res.json();
	return data;
}
```
This function returns the user data so that it is accessible to the application.

Finally, we want to pass the username from the data (called login in the response) into SvelteKit's local storage to allow it to be processed by middleware, and redirect us back to index.svelte.
```
export async function get(event) {
	const code = event.url.searchParams.get("code");
	// Get access token
	const token = await getToken(code);

	// Get user
	const user = await getUser(token);
	event.locals.user = user.login;

	return {
		status: 302,
		headers: {
			location: "/",
		},
	};
}
```

### Middlware
Before we go back to the homepage, we need to implement the middleware. SvelteKit does this in a file called `hooks.js` in the `src` directory. Create this file, and then install a package called cookie:
`npm install cookie`
We then want to import this into `hooks.js` as so:
`import cookie from "cookie"`

We then want to use the `handle()` function included in SvelteKit. to parse cookies into every path within the application.
```
export async function handle({ event, resolve }) {
	// Empty string in case of user not being logged in
	const cookies = cookie.parse(event.request.headers.get("cookie") || "");
	// Store the value from cookies in the local storage
	event.locals.user = cookies.user;
	const response = await resolve(event);

	// Set a cookie of the username stored in sveltekit local storage that is accessable from any directory and is only editble by http
	response.headers.set(
		"set-cookie",
		`user=${event.locals.user || ""}; path=/; HttpOnly`
	);
	return response;
}
```
Then we need to make this value publicly available within the application:
```
export async function getSession(event) {
	return {
		user: event.locals.user,
	};
}
```
We're now ready to use this variable in the homepage.

### Using the data in the homepage
To access the data from the frontend, we need to run some code in the backend. In SvelteKit, we can do this using the module context for a script element.

In `index.svelte`, we want to add the following code to the top of the file to run the `load()` function from SvelteKit:
```
<script context="module">
	export async function load({ session }) {
		return {
			props: {
				user: session.user,
			},
		};
	}
</script>
```
To access this variable, we need to pass it into the homepage as a prop with `export let user` in a regular `<script>` tag.
The further down, if we try to use this in the markup like so:
```
{#if user}
        <h1>Hello {user}</h1>
	<a href="/logout">Logout</a>
{:else}
	<a href="/login">Login</a>
{/if}
```
We should see the follwing displayed after logging in:

![Username being passed into homepage](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6xca9o9cmcuniza24uit.png)

### Persistence
Due to the cookies being given no timeout value, the user will stay logged in until they decide to logout.

### Logging out
We also need to provide the user with a way to logout of the app. We can do this with some simple logic.

Create a file called `logout.js` in the `routes` directory and enter the following code:
```
export async function get(event) {
	event.locals.user = null;

	return {
		status: 302,
		headers: {
			location: "/",
		},
	};
}
```
All this does is remove the user from SvelteKit's local storage and redirect to the homepage.

## Conclusion
I hope people find this useful in the future and put this code to good use. My readme project can be found [here](https://read-me-murex.vercel.app/) and the GitHub repo [here](https://github.com/f-llewellyn/read.me).

You can checkout my [GitHub](https://github.com/f-llewellyn) and contact me if you have any questions.

Also, feel free to give me a job if you'd like, I won't complain.