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
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.
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:
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 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.
Top comments (0)