This coding challenge involves a fullstack app in TypeScript with Next.js using React, Apollo Client (frontend), Nexus Schema and Prisma Client (backend). It uses a SQLite database file with some initial dummy data which you can find at ./prisma/dev.db
.
-
Create a bare clone of the repository. (This is temporary and will be removed so just do it wherever.)
git clone --bare git@github.com:101eth/101-dev-project.git
-
Create a new private repository on Github and name it
101-dev-project
. -
Mirror-push your bare clone to your new
101-dev-project
repository.Replace
<your_username>
with your actual Github username in the url below.cd 101-dev-project.git git push --mirror git@github.com:<your_username>/101-dev-project.git
-
Remove the temporary local repository you created in step 1.
cd .. rm -rf 101-dev-project.git
-
You can now clone your
101-dev-project
repository on your machine (in my case in thecode
folder).cd ~/code git clone git@github.com:<your_username>/101-dev-project.git
Install npm dependencies:
cd 101-dev-project
yarn install
Run the following command to create your SQLite database file. This also creates the User
and Badge
tables that are defined in prisma/schema.prisma
:
yarn prisma migrate dev --name init
When yarn prisma migrate dev
is executed against a newly created database, seeding is also triggered. The seed file in prisma/seed.ts
will be executed and your database will be populated with the sample data.
yarn dev
The app is now running, navigate to http://localhost:3000/
in your browser to explore its UI.
Expand for a tour through the UI of the app
Leaderboard (located in ./pages/index.tsx
)
View profile (located in ./pages/u/[id].tsx
)
You can also access the GraphQL API of the API server directly. It is running on the same host machine and port and can be accessed via the /api
route (in this case that is localhost:3000/api
).
Below are a number of operations that you can send to the API.
query {
users {
id
name
wallet
badges {
badge {
id
name
imageUrl
}
dateEarned
}
}
}
Evolving the application typically requires three steps:
- Migrate your database using Prisma Migrate
- Update your server-side application code
- Build new UI features in React
For the following example scenario, assume you want to add a "profile" feature to the app where users can create a profile and write a short bio about themselves.
The first step is to add a new table, e.g. called Profile
, to the database. You can do this by adding a new model to your Prisma schema file file and then running a migration afterwards:
// ./prisma/schema.prisma
model User {
wallet String @unique
id Int @id @default(autoincrement())
name String?
posts Post[]
+ profile Profile?
}
model BadgeInstance {
id Int @id @default(autoincrement())
User User @relation(fields: [userId], references: [id])
userId Int
Badge Badge @relation(fields: [badgeId], references: [id])
badgeId Int
dateEarned DateTime
}
model Badge {
id Int @id @default(autoincrement())
name String
imageUrl String
instances BadgeInstance[]
}
+model Profile {
+ id Int @default(autoincrement()) @id
+ bio String?
+ user User @relation(fields: [userId], references: [id])
+ userId Int @unique
+}
Once you've updated your data model, you can execute the changes against your database with the following command:
yarn prisma migrate dev --name add-profile
This adds another migration to the prisma/migrations
directory and creates the new Profile
table in the database.
You can now use your PrismaClient
instance to perform operations against the new Profile
table. Those operations can be used to implement queries and mutations in the GraphQL API.
First, add a new GraphQL type via Nexus' objectType
function:
// ./pages/api/index.ts
+const Profile = objectType({
+ name: 'Profile',
+ definition(t) {
+ t.nonNull.int('id')
+ t.string('bio')
+ t.field('user', {
+ type: 'User',
+ resolve: (parent) => {
+ return prisma.profile
+ .findUnique({
+ where: { id: parent.id || undefined },
+ })
+ .user()
+ },
+ })
+ },
+})
const User = objectType({
name: "User",
definition(t) {
t.int("id");
t.string("name");
t.string("wallet");
t.list.field("badges", {
type: "BadgeInstance",
resolve: (parent) =>
prisma.user
.findUnique({
where: { id: Number(parent.id) },
})
.badges(),
});
+ t.field('profile', {
+ type: 'Profile',
+ resolve: (parent) => {
+ return prisma.user.findUnique({
+ where: { id: parent.id }
+ }).profile()
+ }
+ })
},
})
Don't forget to include the new type in the types
array that's passed to makeSchema
:
export const schema = makeSchema({
types: [
Query,
Mutation,
Badge,
BadgeInstance,
User,
+ Profile,
GQLDate
],
// ... as before
}
Note that in order to resolve any type errors, your development server needs to be running so that the Nexus types can be generated. If it's not running, you can start it with yarn dev
.
// ./pages/api/index.ts
const Mutation = objectType({
name: 'Mutation',
definition(t) {
// other mutations
+ t.field('addProfileForUser', {
+ type: 'Profile',
+ args: {
+ wallet: stringArg(),
+ bio: stringArg()
+ },
+ resolve: async (_, args) => {
+ return prisma.profile.create({
+ data: {
+ bio: args.bio,
+ user: {
+ connect: {
+ wallet: args.wallet || undefined,
+ }
+ }
+ }
+ })
+ }
+ })
}
})
Finally, you can test the new mutation like this:
mutation {
addProfileForUser(
wallet: "timb.eth"
bio: "I like turtles"
) {
id
bio
user {
id
name
}
}
}
Expand to view more sample Prisma Client queries on Profile
Here are some more sample Prisma Client queries on the new Profile
model:
const profile = await prisma.profile.create({
data: {
bio: 'Hello World',
user: {
connect: { wallet: 'timc.eth' },
},
},
})
const user = await prisma.user.create({
data: {
wallet: '0x420',
name: 'Jamesy',
profile: {
create: {
bio: 'Hello World',
},
},
},
})
const userWithUpdatedProfile = await prisma.user.update({
where: { wallet: 'timc.eth' },
data: {
profile: {
update: {
bio: 'Hello Friends',
},
},
},
})
Once you have added a new query or mutation to the API, you can start building a new UI component in React. It could e.g. be called profile.tsx
and would be located in the pages
directory.
In the application code, you can access the new operations via Apollo Client and populate the UI with the data you receive from the API calls.
- Check out the Prisma docs
- Create issues and ask questions on GitHub
- Watch our biweekly "What's new in Prisma" livestreams on Youtube
Let's get to the meat and potatoes of what you'll be working on.
- Make 100% sure your repo is private (Github now allows unlimited private repos)
- Add Alex (
alec2435
) and Tim (timconnorz
) to your repo on Github - Don't spend more than 3 hours on this project (this isn't meant to be some project where you overengineer this for extra credit)
- Ask Alex clarifying questions
- Glhf
Try to complete as many of these as you can. They are relatively speaking in increasing order of how much time they should take
This repo has a few very obvious bugs (don't waste time looking for subtle stuff). Do your best to find and fix them.
The leaderboard page takes a really long time to load for a homepage. Try to optimize the load times.
Hints:
- We're not looking for single digit milisecond optimizations, there's some really big obvious problems
- Data fetching and the type of rendering used might be helpful avenues to look down
For this challenge, right under the seach bar add a connect wallet button. This button should just take the current user's wallet address and attempt to open their own profile.
Hints:
- There are no bonus points for reinventing the wheel, use libraries to your advantage
- Add your own wallet address to the database using prisma studio (
yarn prisma studio
) for testing - Try to match the designs as best you can
- Don't overcomplicate this lol
This one is self explanitory, but don't error out if a wallet is connected that we have never seen before. For bonus points add some text to the page to inform the user they have no badges.
Right now the search only allows you to type a wallet/id, press enter, then it opens that user if they exist. Ideally we'd like some sort of basic typeahead/suggestion feature where the search bar in real time searches the database and provides suggestions.
(Find what this looks like in the designs)
The exact way you add these visually doesn't really matter, but embed the course viewer from the main site, use our docs and asking Alex for help with this.
Examples of how you can do this:
- Add some random courses from 101.xyz under the leaderboard with a header like
Level Up, Take Some Courses
- Show courses to wallet-connected users on their profiles
These are rough designs to give you an idea of what we're looking for.
- Wallet connection stuff: RainbowKit, Wagmi, Web3Modal
- Nexus GraphQL docs
- Tailwind Docs
- Next.js Docs (my fav page)
- 101 "docs"
Use this area to note down your progress
-
1. Fix the bugs
-
NOTES:
-
-
2. Optimize the leaderboard page
-
NOTES:
-
-
3. Add a connect wallet button to the home page
-
NOTES:
-
-
3.1 Create a new user if a wallet is connected that hasn't been seen before
-
NOTES:
-
-
4. Make the search more searchy
-
NOTES:
-
-
5. [Bonus] Embed some courses
-
NOTES: (Please mention where you put this if you did)
-