How to use the GitHub rest API with SvelteKit

fllewellyn

f-llewellyn

Posted on May 2, 2022

How to use the GitHub rest API with SvelteKit

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. 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:

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
Enter fullscreen mode Exit fullscreen mode

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 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
Your .env file should look something like this, but with your apps' details:

VITE_CLIENT_ID=****cc378
VITE_CLIENT_SECRET=****984b
Enter fullscreen mode Exit fullscreen mode

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}`,
        },
    };
}
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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: "/",
        },
    };
}
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

Then we need to make this value publicly available within the application:

export async function getSession(event) {
    return {
        user: event.locals.user,
    };
}
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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}
Enter fullscreen mode Exit fullscreen mode

We should see the follwing displayed after logging in:

Username being passed into homepage

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: "/",
        },
    };
}
Enter fullscreen mode Exit fullscreen mode

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 and the GitHub repo here.

You can checkout my GitHub and contact me if you have any questions.

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

💖 💪 🙅 🚩
fllewellyn
f-llewellyn

Posted on May 2, 2022

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related